Добрый день. В этой статье я хотел бы поделиться своим первым знакомством с такими вещами как Maven, Spring, Hibernate, MySQL и Tomcat в процессе создания простого CRUD приложения. Это первая часть из 4. Статья рассчитана в первую очередь на тех, кто уже прошел здесь 30-40 уровней, но за пределы чистой джавы пока не выбирался и только начинает (или собирается начинать) выходить в открытый мир со всеми этими технологиями, фреймворками и прочими незнакомыми словами. Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 1

Содержание:

Введение

Знакомство с новыми для меня технологиями и фреймворками я начинал с изучения различных примеров, в которых они использовались, потому что обычно я лучше всего что-то понимаю, когда вижу работу на примере полноценного приложения. Обычно в качестве таких примеров выступают CRUD-приложения (Create, Read, Update, Delete), в интернете полно таких примеров разной степени сложности. Проблема в том, что там обычно не объясняют подробно как, что и зачем там сделано, почему добавлена такая-то зависимость, почему нужен именно такой класс и т.д. В большинстве случаев берут уже полностью готовое приложение, с итоговым POM файлом, с конечными вариантами классов и просто пробегают по каждому, не заостряя внимания на мелочах, которые для опытного человека, наверное, кажутся очевидными. Я много таких примеров разобрал и обычно вроде как и понятно, как там все работает, но вот как к этому пришли не совсем ясно. Поэтому я решил будет не лишним такой пример не с позиции опытного разработчика, а с позиции новичка, который ни разу не имел дело со Spring, Hibernate и прочими вещами.
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 2
Я постараюсь как можно подробнее (насколько позволит мне мое понимание) описать весь свой путь создания CRUD приложения, начиная с чего-то простейшего уровня Hello World. В первую очередь я это делаю для самого себя, ибо когда что-то пытаешься описать, рассказать, объяснить, оно и в своей голове гораздо лучше усваивается и упорядочивается. Но если кому-то это будет полезно и поможет с чем-нибудь разобраться буду очень рад. В этом примере попробуем создать простое CRUD приложение с использованием Maven, Tomcat, Spring, Hibernate и MySQL. Предварительные шаги, такие как установка Maven, MySQL, использование Ultimate версии идеи и т.д. думаю нет нужды подробно расписывать, с этим проблем возникнуть не должно. Стоит отметить, что в этом примере настройка конфигурации будет происходить при помощи джава классов (что называется JavaConfig) без использования xml.

Создание проекта

Итак, поскольку я новичек, то использовать какие-то непонятные архетипы не будем. Spring initializr так и вовсе пока звучит слишком страшно. Поэтому создадим самый обычный простой Maven проект. У меня нет доменного имени, так что в groupid напишу просто testgroup, а в artifactid напишу название, ну например, filmography (это будет список фильмов). Создаем проект и выбираем Enable auto-import когда идея это предложит. Благодаря этому каждый раз, когда мы будем вносить какие-либо изменения в POM файл (Project Object Model, в этом файле описывается вся структура Maven проекта) все сразу же автоматически будем применяться к проекту. Библиотеки будут браться из нашего локального репозитория, если они уже имеются у нас в наличии, либо, если будем использовать какие-то новые зависимости, с которыми раньше дел не имели, то Maven просто скачает их через интернет из центрального репозитория. Еще у Maven есть функция загружать исходники и документацию (Download Sources and/or Documentation). Тоже очень удобно, если что-то не понятно с каким-то классом или методом, можно зайти в исходники и посмотреть, как оно там все внутри устроено. Добавим пару деталей. Это будет веб-приложение, и мы будем использовать Tomcat. Для развертывания (deploy) приложения в Tomcat нужно передать его туда в виде war архива (Web Application Resource, специальный формат для веб-приложений). Для этого добавим в POM файл такую строчку, чтобы приложение собиралось в war архив:
<packaging>war</packaging>
Ну и еще понадобится специальная директория для веб-исходников, в нашем случае там будут лежать jsp страницы, какие-то веб-ресурсы. Создадим в main директорию webapp. Она должна называться именно так и находиться именно в main, так же как java и resources, потому что это стандартная структура каталогов Maven. Когда мы установили упаковку в war и тем самым определили, что это веб-проект, директория webapp будет автоматически помечена как Web application sources (на ней будет голубая точка) и все, что касается веб, будет искаться в этой папке. И еще один момент. По-умолчанию Maven использует версию языка 1.5, но я хочу использовать, например, версию 1.8 – Java 8 (Можно и 10 взять, или 11, но все равно никакие фишки оттуда использовать не планируется, так что пускай 8 будет). Решается это очень просто, пишем в гугле что-то вроде "Maven java 8" и смотрим что нужно добавить в POM файл, чтобы Maven компилировал наши классы для нужной версии. В итоге имеем следующее: Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 3

Подключение Spring MVC

Нужно с чего-то начать. По плану мы будем подключать базу данных, использовать Hibernate, но это все пока звучит как-то слишком страшно. Нужно сперва заняться чем-нибудь попроще. Spring MVC, это уже получше, с MVC паттерном уже давным-давно знакомы, он использовался в половине больших задач курса. Отсюда и начнем плясать. Для создания веб приложения со Spring MVC нам также понадобится Servlet-API, т.е. та штука, с помощью которой будет происходить взаимодействие запрос-ответ. Попробуем подключить это. Заходим в гугл, ищем нужные зависимости в репозитории Maven и добавляем их в pom.xml. Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 4В разделе External Libraries видно, что подгрузилась не только spring-webmvc, но и куча чего еще. Т.е. нам не нужно дополнительно подключать зависимости для spring core, context, beans и т.д. которые нам понадобятся, все необходимое подтянулось само вместе со spring-webmvc.

Нужно сделать небольшую оговорку. Обычно рекомендуется все равно отдельно добавлять зависимость для каждой используемой библиотеки, даже если они и так идут в комплекте с уже добавленными, т.к. это может помочь избежать некоторых проблем и глюков.

Простой пример. Допустим мы добавили зависимость, которая использует какое-то API, и заодно она подтянет для этого API какую-то реализацию. А потом мы добавили другую зависимость, которая использует тоже самое API и тоже подтягивает для этого какую-то его реализацию, но уже другую. Таким образом у нас в наличие будет 2 разные реализации одного и того же API. И если мы сами захотим где-то использовать какие-то методы этого API, тогда и возникнет проблема, ведь система не будет знать, какую именно реализацию нужно использовать, она выберет случайно, возможно не ту, что мы ожидали. А если явно указать зависимость для одной из реализаций, то приоритет будет отдан именно ей.

Однако это не такая строгая рекомендация, это касается в основном крупных проектов, где используется множество рзличных библиотек от разных компаний. Тут мы так делать не будем, дабы не загружать слишком сильно POM файл, каких-то проблем не предвидится. Но тем не менее все же стоит иметь это ввиду.

Еще одно замечание. Что означает provided в зависимости javax.servlet-api. Scope — это область действия зависимости, provided означает что зависимость будет доступна на этапе компиляции и тестирования приложения, но в архив она помещена не будет. Дело в том, что для развертывания (deploy) приложения мы будем использовать контейнер сервлетов, Tomcat, а у него внутри уже есть такие библиотеки, поэтому нет нужды передавать их туда и утяжелять архив лишним грузом. Забегая вперед, по той же причине мы обойдемся без привычного метода main, потому что он уже есть внутри Tomcat.

Создание страниц и контроллера

Попробуем теперь состряпать что-нибудь простенькое. Для начала создадим в webapp дополнительную директорию, например pages, в которой будут храниться наши представления (view), т.е. jsp-страницы, и создадим пару страниц. Нам понадобится страница, на которой в будущем будет отображаться список фильмов, допустим films.jsp, и еще, пожалуй, можно сделать отдельную страницу для редактирования, пускай это будет editPage.jsp. Пока ничем серьезным их заполнять не будем, просто для пробы сделаем на одной странице ссылку на другую. Теперь нужен класс, который будет обрабатывать запросы, т.е. контроллер. Добавим новый пакет controller и создадим в нем класс FilmController (вообще не обязательно расфасовывать все по разным пакетам, это приложение будет очень маленькое и простое, но в нормальном проекте может быть много контроллеров, классов конфигурации, моделей и т.д., поэтому даже начиная с маленьких проектов лучше сразу привыкать делать все упорядоченно и структурировано, чтобы не было каши). В этом классе создадим методы, которые будут возвращать наши представления в ответ на запросы.
package testgroup.filmography.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class FilmController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView allFilms() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("films");
        return modelAndView;
    }

    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    public ModelAndView editPage() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("editPage");
        return modelAndView;
    }
}
Что тут к чему. У Spring MVC есть такая штука, как DispatcherServlet. Это как бы главный контроллер, все входящие запросы проходят через него и он уже дальше передает их конкретному контроллеру. Аннотация @Controller как раз и сообщает Spring MVC, что данный класс является контроллером (ну логично в общем-то), диспетчер будет проверять аннотации @RequestMapping чтобы вызвать подходящий метод. Аннотация @RequestMapping позволяет задать адреса методам контроллера, по которым они будут доступны в клиенте (браузер). Ее можно применять также и к классу контроллера, чтобы задать, так сказать, корневой адрес для всех методов. Для метода allFilms() параметр value установлен "/", поэтому он будет вызван сразу, когда в браузере будет набрана комбинация http://host:port/ (т.е. по-умолчанию это http://localhost:8080/ или http://127.0.0.1:8080/). Параметр method указывает кокой тип запроса поддерживается (GET, POST, PUT и т.д.). Поскольку тут мы только получаем данные то используется GET. Позднее, когда появятся методы для добавления и редактирования там уже будут POST запросы. (Кстати вместо аннотации @RequestMapping с указанием метода, можно использовать аннотации @GetMapping, @PostMapping и т.д. @GetMapping эквивалентно @RequestMapping(method = RequestMethod.GET)). В наших методах создаем объект ModelAndView и устанавливаем имя представления, которое нужно вернуть.

Конфигурация

Перейдем к настройке конфигурации. Создадим в пакете config класс WebConfig. В нем будет только один метод возвращающий объект типа ViewResolver, это такой интерфейс, необходимый для нахождения представления по имени.
package testgroup.filmography.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "testgroup.filmography")
public class WebConfig {

    @Bean
    ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/pages/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}
@Configuration сообщает Spring что данный класс является конфигурационным и содержит определения и зависимости bean-компонентов. Бины (bean) — это объекты, которые управляются Spring'ом. Для определения бина используется аннотация @Bean. @EnableWebMvc позволяет импортировать конфигурацию Spring MVC из класса WebMvcConfigurationSupport. Можно также реализовать, например, интерфейс WebMvcConfigurer, у которого есть целая куча методов, и настроить все по своему вкусу, но нам незачем пока в это углубляться, хватит и стандартных настроек. @ComponentScan сообщает Spring где искать компоненты, которыми он должен управлять, т.е. классы, помеченные аннотацией @Component или ее производными, такими как @Controller, @Repository, @Service. Эти аннотации автоматически определяют бин класса. В методе viewResolver() мы создаем его реализацию и определяем где именно искать представления в webapp. Поэтому когда в методе контроллера мы устанавливали имя "films" представление найдется как "/pages/films.jsp" Итак, класс конфигурации у нас есть, но пока что это просто какой-то отдельный класс, он сам по себе и на наше приложение никак не влияет. Нам нужно зарегистрировать эту конфигурацию в контексте Spring. Для этого нужен класс AbstractAnnotationConfigDispatcherServletInitializer. В пакете config создаем его наследника, допустим AppInitializer, и реализуем его методы.
package testgroup.filmography.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
В последнем методе регистрируются адреса и еще есть 2 метода для регистрации классов конфигурации. Веб-конфигурации, где определяются ViewResolver'ы и тому подобное, помещаем в getServletConfigClasses(). Обо всем этом лучше почитать в документации и разных гайдах, но в нашем случае не обязательно пока в это углубляться, наш WebConfig в принципе можно и в RootClasses определить, можно даже и в оба сразу, все равно будет работать. Еще один момент. Возможно будут проблемы с кодировкой, когда при отправке с формы значений с русскими символами в результате будут получаться каракули. Для решения этой проблемы добавим фильтр, который будет заниматься предварительной обработкой запросов. Заходим в класс AppInitializer и переопределяем метод getServletFilters, в котором укажем нужную кодировку, она разумеется должна быть такой же как и везде, как на страницах и в базе данных:
protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        return new Filter[] {characterEncodingFilter};
    }
Ну вроде все настроили, можно попробовать запустить и посмотреть, что получилось. Run -> Run -> Edit Configurations -> Add New Configuration -> Tomcat Server -> Local Далее нужно выбрать артефакт для развертывания. Идея сама даст подсказку Warning: No artifacts marked for deployment. Жмем кнопку fix и выбираем ...: war exploded. Либо можно зайти в Deployment -> add -> Artifact -> ...: war exploded. Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 6И еще нужно зайти в Deployment и установить в поле Applecation context (это будет частью url адреса, по которому приложение будет доступно в браузере) значение "/". Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 7Тогда наше приложение будет сразу доступно по адресу http://localhost:8080/ (но также можно указать там что-то, ну например "/filmography", и тогда просто нужно будет добавлять это ко всем адресам, т.е. например будет не "http://localhost:8080/edit", а будет "http://localhost:8080/filmography/edit"). Нажимаем Run и ждем пока запустится. Вот что у меня получилось: Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 8Вроде бы все хорошо, но есть тут один нюанс. Дело в том, что наши страницы сейчас общедоступны и доступ к ним можно получить напрямую, написав путь в адресной строке. Вводим http://localhost:8080/pages/films.jsp и вот мы без ведома контроллера получили нашу страницу. Как-то это не очень правильно, поэтому мы создадим в webapp специальную директорию WEB-INF. То, что внутри, будет скрыто для публики и получить доступ можно будет только через контроллер. Помещаем директорию с нашими представлениями (pages) в WEB-INF, а в ViewResolver соответственно добавляем ее в префикс:
viewResolver.setPrefix("/WEB-INF/pages/");
Теперь по адресу http://localhost:8080 получаем нашу страницу, а если попытаться напрямую http://localhost:8080/WEB-INF/pages/films.jsp то получим ошибку 404. Ну вот, замечательно, у нас получилось простейшее веб-приложение, Hello World что называется. Структура проекта на данный момент выглядит так:
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 9

Модель

У нас уже есть представления и контроллер, но в MVC есть еще и 3-я буква, поэтому для полноты картины добавим еще и модель. В пакете model создадим класс Film ну, например, c такими полями: int id, String title (название), int year (год выхода), String genre (жанр) и boolean watched (т.е. смотрели уже этот фильм или нет).
package testgroup.filmography.model;

public class Film {
    private int id;
    private String title;
    private int year;
    private String genre;
    private boolean watched;
// + Getters and setters
}
Ничего особенного, самый обыкновенный класс, приватные поля, геттеры и сеттеры. Объекты таких классов еще называют POJO (Plain Old Java Object), ну т.е. "простой джава объект". Попробуем теперь создать такой объект и вывести его на странице. Пока не будем особо париться как его создавать, инициализировать. Для пробы просто тупо создадим его прямо в контроллере, например, вот так:
public class FilmController {
    private static Film film;

    static {
        film = new Film();
        film.setTitle("Inception");
        film.setYear(2010);
        film.setGenre("sci-fi");
        film.setWatched(true);
    }
И добавим этот объект в наш ModelAndView с помощью метода addObject:
@RequestMapping(method = RequestMethod.GET)
    public ModelAndView allFilms() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("films");
        modelAndView.addObject("film", film);
        return modelAndView;
    }
Теперь мы сможем на нашей странице вывести этот объект. В films.jsp вместо Hello World напишем ${film} и сюда подставится объект, которому соответствует имя атрибута "film". Попробуем запустить и посмотреть что получилось (для понятного вывода объекта у класса Film был переопределен toString()):
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 10

Model-View-Controller

На данном этапе мы уже, вроде как, имеем полноценное Spring MVC приложение. Прежде чем двигаться дальше хорошо бы еще раз окинуть все взглядом и разобраться как оно все работает. В интернете по этому поводу можно найти множество картинок и схем, мне нравится вот эта:
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 11
Когда мы пишем в строке браузера запрос, его принимает Dispatcher Servlet, далее он находит для обработки этого запроса подходящий контроллер с помощью HandlerMapping (это такой интерфейс для выбора контроллера, проверяет в каком из имеющихся контроллеров есть метод, принимающий такой адрес), вызывается подходящий метод и Controller возвращает информацию о представлении, затем диспетчер находит нужное представления по имени при помощи ViewResolver'а, после чего на это представление передаются данные модели и на выход мы получаем нашу страничку. Как-то так. Продолжение следует... Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 2) Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 3) Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 4)