JavaRush /Java блог /Random UA /Знайомство з Maven, Spring, MySQL, Hibernate та перший CR...
Макс
41 рівень

Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 1)

Стаття з групи Random UA
Добридень. У цій статті я хотів би поділитися своїм першим знайомством з такими речами як Maven, Spring, Hibernate, MySQL та Tomcat у процесі створення простого CRUD програми. Це перша частина з чотирьох. іншими незнайомими словами. Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 1) - 1

Зміст:

Вступ

Знайомство з новими для мене технологіями та фреймворками я починав з вивчення різних прикладів, в яких вони використовувалися, тому що зазвичай я найкраще щось розумію, коли бачу роботу на прикладі повноцінної програми. Зазвичай як такі приклади виступають CRUD -додатки ( C reate, R ead, U pdate, 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) - 5І ще потрібно зайти до Deployment та встановити у поліApplecation context (це буде частиною url адресаи, за якою програма буде доступна в браузері) значення " /". Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 1) - 6Тоді наш додаток буде відразу доступний за адресаою http://localhost:8080/ (але також можна вказати там щось, ну наприклад " /filmography", і тоді просто потрібно буде додавати це до всіх адреса, тобто наприклад буде не " http://localhost:8080/edit" , а буде "http://localhost:8080/filmography/edit" ). Натискаємо Run і чекаємо, поки запуститься. Ось що в мене вийшло: Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 1) - 7Начебто все добре, але є тут один нюанс. Справа в тому, що наші сторінки зараз є загальнодоступними і доступ до них можна отримати безпосередньо, написавши шлях в адресаному рядку. Вводимо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) - 8

Модель

У нас вже є уявлення та контролер, але в MVC є ще й 3-я буква, тому для повноти картини додамо ще й модель. У пакеті modelстворимо клас Filmну, наприклад, з такими полями: 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.jspHello World напишемо ${film}і сюди підставиться об'єкт, якому відповідає ім'я атрибута " film". Спробуємо запустити і подивитися що вийшло (для зрозумілого виведення об'єкта у класу Filmбуло перевизначено toString()):
Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 1) - 9

Model-View-Controller

На даному етапі ми вже, начебто, маємо повноцінний Spring MVC додаток. Перш ніж рухатися далі, добре б ще раз окинути все поглядом і розібратися як воно все працює. В інтернеті з цього приводу можна знайти безліч картинок та схем, мені подобається ось ця:
Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 1) - 10
Коли ми пишемо в рядку браузера запит, його приймає 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)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ