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

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

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

Зміст:

Ну що ж, рухатимемося далі, спробуємо тепер начарувати ціле сховище фільмів. У нашому маленькому і простенькому додатку, звичайно, можна просто тупо запиляти всю логіку прямо в контролері, але, як уже зазначалося, краще відразу вчитися робити все правильно. Тому зрозуміємо кілька шарів. У нас буде DAO , що відповідає за роботу з даними, Service , де буде будь-яка інша логіка, та і Controller буде тільки обробляти запити і викликати необхідні методи сервісу.

Data Access Object

Data Access Object (DAO) – це такий патерн проектування. Сенс у тому, щоб створити спеціальний прошарок, який відповідатиме виключно за доступ до даних (робота з базою даних або іншим механізмом зберігання). У пакеті daoстворимо інтерфейс, FilmDAOв якому будуть такі методи, як додати, видалити і т.д. Я їх назвав трохи інакше, але вони відповідають основним CRUD операціям ( C reate, R ead, U pdate, D elete).

Тут варто відзначити, що крім DAO існує ще й такий підхід як Repository, вони, як дуже схожі, обидва використовуються для роботи з даними. Я поки що не розібрався, які у цих підходів особливості та яка між ними різниця. Тому я, можливо, тут помиляюся і це слід називати саме репозиторієм, а не дао, а може це взагалі щось середнє. Але в більшості прикладів, які я бачив та вивчав, це називають саме DAO, так що і я, мабуть, назву так само. При цьому, можливо, десь далі текстом вживу слово репозиторій. У будь-якому випадку, якщо я з цим десь не прав, прошу мене пробачити.

package testgroup.filmography.dao;

import testgroup.filmography.model.Film;

import java.util.List;

public interface FilmDAO {
    List<Film> allFilms();
    void add(Film film);
    void delete(Film film);
    void edit(Film film);
    Film getById(int id);
}
Тепер нам потрібна його реалізація. Підключати базу даних поки не будемо, все ще страшнувато. Щоб потренуватися і звикнути для початку, імітуємо сховище в пам'яті, створимо список з кількома фільмами. Для зберігання списку будемо використовувати не List, а Mapщоб було зручно отримувати конкретний фільм за його id, не перебираючи для цього весь список. Для генерації idвикористовуємо AtomicInteger . Створимо клас FilmDAOImpl, реалізуємо всі методи та заповнимо карту. Щось на зразок цього.
package testgroup.filmography.dao;

import testgroup.filmography.model.Film;

import java.util.*;

public class FilmDAOImpl implements FilmDAO {
    private static final AtomicInteger AUTO_ID = new AtomicInteger(0);
    private static Map<Integer, Film> films = new HashMap<>();

    static {
        Film film1 = new Film();
        film1.setId(AUTO_ID.getAndIncrement());
        film1.setTitle("Inception");
        film1.setYear(2010);
        film1.setGenre("sci-fi");
        film1.setWatched(true);
        films.put(film1.getId(), film1);

        // + film2, film3, film4, ...
    }
    @Override
    public List<Film> allFilms() {
        return new ArrayList<>(films.values());
    }

    @Override
    public void add(Film film) {
        film.setId(AUTO_ID.getAndIncrement());
        films.put(film.getId(), film);
    }

    @Override
    public void delete(Film film) {
        films.remove(film.getId());
    }

    @Override
    public void edit(Film film) {
        films.put(film.getId(), film);
    }

    @Override
    public Film getById(int id) {
        return films.get(id);
    }
}

Service

Тепер додамо сервісний шар. У принципі, в даному прикладі цілком можна обійтися і без нього, обмежившись DAO, додаток буде дуже простий і якоїсь складної логіки в сервісі не планується. Але раптом потім у майбутньому захочеться додати у проект усіляких складнощів та цікавостей, тому для повноти картини таки буде. Поки що в ньому просто викликатимуться методи з DAO. У пакеті serviceстворимо інтерфейс FilmService.
package testgroup.filmography.service;

import testgroup.filmography.model.Film;

import java.util.List;

public interface FilmService {
    List<Film> allFilms();
    void add(Film film);
    void delete(Film film);
    void edit(Film film);
    Film getById(int id);
}
І його реалізація:
package testgroup.filmography.service;

import testgroup.filmography.dao.FilmDAO;
import testgroup.filmography.dao.FilmDAOImpl;
import testgroup.filmography.model.Film;

import java.util.List;

public class FilmServiceImpl implements FilmService {
    private FilmDAO filmDAO = new FilmDAOImpl();

    @Override
    public List<Film> allFilms() {
        return filmDAO.allFilms();
    }

    @Override
    public void add(Film film) {
        filmDAO.add(film);
    }

    @Override
    public void delete(Film film) {
        filmDAO.delete(film);
    }

    @Override
    public void edit(Film film) {
        filmDAO.edit(film);
    }

    @Override
    public Film getById(int id) {
        return filmDAO.getById(id);
    }
}
Структура проекту тепер виглядає так:
Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 2) - 1

Контролер та уявлення

Попрацюємо тепер над методами контролера та наповненням сторінок. При заповненні сторінок нам знадобляться деякі прийоми. Наприклад, щоб вивести список фільмів потрібен цикл, якщо, припустимо, хочемо міняти якийсь напис, залежно від параметрів, потрібні умови і т.д. Формат JSP (JavaServer Pages) дозволяє використовувати вставки java-коду, з якими це можна реалізувати. Але використовувати на сторінці java-код упереміш з HTML-кодом не хочеться. Це було б, як мінімум, дуже негарно. На щастя, для вирішення цієї проблеми існує така чудова штука, як JSTL (JavaServer Pages Standard Tag Library) або Стандартна Бібліотека Тегів JSP. Вона дозволяє використовувати в наших jsp-сторінках цілу купу додаткових тегів для різних потреб. Підключимо її в pom.xml:
<dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
</dependency>
Тепер займемося контролером. Насамперед приберемо звідти створення об'єкта Film, це робилося для проби і більше нам не потрібно. Додамо туди сервіс і викликатимемо його методи.
public class FilmController {
    private FilmService filmService = new FilmServiceImpl();
Ну і відповідно зробимо методи для кожного випадку додати, видалити і т.д. Спочатку метод для відображення головної сторінки зі списком фільмів:
@RequestMapping(method = RequestMethod.GET)
    public ModelAndView allFilms() {
        List<Film> films = filmService.allFilms();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("films");
        modelAndView.addObject("filmsList", films);
        return modelAndView;
    }
Тут нічого нового. Отримуємо список фільмів із сервісу та додаємо його в модель. Тепер зробимо головну сторінку, films.jspяку повертає цей метод:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>FILMS</title>
</head>
<body>

<h2>Films</h2>
<table>
    <tr>
        <th>id</th>
        <th>title</th>
        <th>year</th>
        <th>genre</th>
        <th>watched</th>
        <th>action</th>
    </tr>
    <c:forEach var="film" items="${filmsList}">
        <tr>
            <td>${film.id}</td>
            <td>${film.title}</td>
            <td>${film.year}</td>
            <td>${film.genre}</td>
            <td>${film.watched}</td>
            <td>
                <a href="/edit/${film.id}">edit</a>
                <a href="/delete/${film.id}">delete</a>
            </td>
        </tr>
    </c:forEach>
</table>

<h2>Add</h2>
<c:url value="/add" var="add"/>
<a href="${add}">Add new film</a>
</body>
</html>
Розглянемо цю сторінку докладніше, що тут взагалі до чого. - тут підключається JSTL core, яка включає основні теги створення циклів, умов і т.д.
  • <table>- Тег для створення таблиці.
  • <tr>- Рядок таблиці
  • <th>- Заголовок стовпця
  • <td>- осередок таблиці
Спочатку робимо рядок-шапку таблиці з назвами стовпців. Потім <c:forEach var="film" items="${filmsList}">- в циклі (який ми взяли з JSTL core) пробігаємося по всіх елементах переданого списку ( filmsList), для кожного елемента ( film) створюємо новий рядок і в кожну комірку записуємо відповідне значення. Тут є момент, запис ніби film.idтреба розуміти як film.getId(), тобто. не безпосередньо до поля звернення, саме геттер викликається. В останньому стовпці ( action) робимо посилання для видалення та редагування (відповідні методи зараз зробимо). Ну й унизу посилання на метод додавання нового фільму. Ось як це виглядає: Далі займемося методом, який повертатиме сторінку редагування конкретного фільму:
@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
    public ModelAndView editPage(@PathVariable("id") int id) {
        Film film = filmService.getById(id);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("editPage");
        modelAndView.addObject("film", film);
        return modelAndView;
    }
Тут з'явилося дещо нове - це інструкція @PathVariable. Вона вказує на те, що цей параметр ( int id) Виходить з адресаного рядка. Щоб вказати місце цього параметра в адресаному рядку використовується конструкція {id}(до речі, якщо ім'я змінної збігається, як у даному випадку, то в дужках це можна не вказувати, а просто написати @PathVariable int id). Отже, на головній сторінці ми зробабо посилання для кожного фільму із зазначенням id:
<a href="/edit/${film.id}">edit</a>
Потім це значення присвоюється параметр методу і далі по ньому ми через сервіс з репозиторію отримуємо конкретний фільм і додаємо його в модель. Це був метод для отримання сторінки редагування, тепер потрібен метод для редагування:
@RequestMapping(value = "/edit", method = RequestMethod.POST)
    public ModelAndView editFilm(@ModelAttribute("film") Film film) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/");
        filmService.edit(film);
        return modelAndView;
    }
У методі editPageми додали модель атрибут:
modelAndView.addObject("film", filmService.getById(id));
І тепер за допомогою анотації @ModelAttributeми отримуємо цей атрибут і можемо змінити його. Метод запиту POSTтому, що тут ми передаватимемо дані. " redirect:/" означає, що після виконання цього методу ми будемо перенаправлені на адресау " /", тобто. запуститься спосіб allFilmsі ми повернемося на головну сторінку. Тепер зробимо саму сторінку editPage.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Edit</title>
</head>
<body>
<c:url value="/edit" var="var"/>
<form action="${var}" method="POST">
    <input type="hidden" name="id" value="${film.id}">
    <label for="title">Title</label>
    <input type="text" name="title" id="title">
    <label for="year">Year</label>
    <input type="text" name="year" id="year">
    <label for="genre">Genre</label>
    <input type="text" name="genre" id="genre">
    <label for="watched">Watched</label>
    <input type="text" name="watched" id="watched">
    <input type="submit" value="Edit film">
</form>
</body>
</html>
  • <form>— форма для збору та відправлення даних, із зазначенням хто їх оброблятиме ( /edit)
  • <input>- Елементи інтерфейсу для взаємодії з користувачем (кнопки, поля введення і т.д.)
  • <label>- Текстова мітка
Отже, при натисканні кнопки <input type="submit" value="Edit film">дані форми будуть відправлені на сервер (спеціально додано невидиме поле зі значенням id, щоб сервер знав який саме запис в БД потрібно оновити). У методі editFilmвони будуть присвоєні відповідним полям атрибуту film. Потім ми повернемось на головну сторінку із оновленим списком. Виглядає сторінка редагування так: Тепер займемося додаванням нових фільмів до списку. Для цього також знадобиться форма для введення та надсилання даних. Можна зробити форму на головній сторінці або можна зробити окрему сторінку на кшталт editPage.jsp. Але, з іншого боку, форма для додавання буде точно така ж, як і для редагування, тобто. 4 поля для введення та кнопка відправки. То навіщо тоді створювати нову сторінку, знову використовуємоeditPage.jsp. Метод для отримання сторінки:
@RequestMapping(value = "/add", method = RequestMethod.GET)
    public ModelAndView addPage() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("editPage");
        return modelAndView;
    }
У методі editPageми додатково передавали атрибут, щоб потім змінити його, а тут ми просто отримуємо сторінку. І метод додавання:
@RequestMapping(value = "/add", method = RequestMethod.POST)
    public ModelAndView addFilm(@ModelAttribute("film") Film film) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/");
        filmService.add(film);
        return modelAndView;
    }
Оскільки атрибут ми сюди не передавали, тут буде створено новий об'єкт Film. Ну а так тут, в принципі, нічого нового. Варто також звернути увагу, що у нас обидва методи доступні на адресау " /add". Це можливо завдяки тому, що вони реагують різні типи запиту. Переходячи за посиланням на головній сторінці, ми робимо GET-запит, що приводить нас у метод addPage. А коли на сторінці додавання ми тиснемо кнопку надсилання даних, робиться POST-запит, за це вже відповідає метод addFilm. Для додавання нового фільму ми вирішабо використати ту саму сторінку, що й для редагування. Але там дані відправляються на адресау " /edit":
<c:url value="/edit" var="var"/>
<form action="${var}" method="POST">
    <input type="submit" value="Edit film">
</form>
Нам потрібно трохи підправити сторінку, щоб вона поводилася по-різному для додавання та редагування. Для вирішення цього питання скористаємося умовами з тієї ж бібліотеки JSTL core:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <c:if test="${empty film.title}">
        <title>Add</title>
    </c:if>
    <c:if test="${!empty film.title}">
        <title>Edit</title>
    </c:if>
</head>
<body>
<c:if test="${empty film.title}">
    <c:url value="/add" var="var"/>
</c:if>
<c:if test="${!empty film.title}">
    <c:url value="/edit" var="var"/>
</c:if>
<form action="${var}" method="POST">
    <c:if test="${!empty film.title}">
        <input type="hidden" name="id" value="${film.id}">
    </c:if>
    <label for="title">Title</label>
    <input type="text" name="title" id="title">
    <label for="year">Year</label>
    <input type="text" name="year" id="year">
    <label for="genre">Genre</label>
    <input type="text" name="genre" id="genre">
    <label for="watched">Watched</label>
    <input type="text" name="watched" id="watched">
    <c:if test="${empty film.title}">
        <input type="submit" value="Add new film">
    </c:if>
    <c:if test="${!empty film.title}">
        <input type="submit" value="Edit film">
    </c:if>
</form>
</body>
</html>
Тобто. ми просто перевіряємо поле film.title. Якщо воно порожнє, це новий фільм, ми повинні заповнити для нього всі дані і додати до списку. Якщо це поле не порожнє, це фільм зі списку і його потрібно просто змінити. Т.о. отримуємо два варіанти нашої сторінки: Ну і останній метод контролера для видалення фільму зі списку:
@RequestMapping(value="/delete/{id}", method = RequestMethod.GET)
    public ModelAndView deleteFilm(@PathVariable("id") int id) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/");
        Film film = filmService.getById(id);
        filmService.delete(film);
        return modelAndView;
    }
Думаю, немає потреби тут щось коментувати, це вже розглянули. Посилання на цю адресау на головній сторінці ми вже зробабо. Ну що ж, тут ніби все готове, можна ще раз запустити і подивитися, як все працює.

Repository та Service як компоненти Spring

Зробимо ще одну невелику поправку. Справа в тому, що зараз наші сховища та сервіс – це просто класи, і щоб їх використовувати доводиться самим створювати об'єкт класу ( new FilmServiceImpl()). Але ж у нас не просто так підключений Spring , так нехай він сам цю справу і контролює. Щоб віддати наші класи під управління Spring'а, потрібно зазначити, що вони є компонентами. Для цього відзначимо їх спеціальними інструкціями:
@Repository
public class FilmDAOImpl implements FilmDAO {
@Service
public class FilmServiceImpl implements FilmService {
Анотації @Repositoryі @Service, як і @Controllerє похідними від @Component. У чому конкретні особливості та відмінності цих трьох анотацій і чим вони відрізняються від простого компонента, варто почитати окремо в документації чи гайдах. Поки ж достатньо знати, що ці інструкції повідомляють Spring про те, що ці класи є репозиторієм та сервісом відповідно. І тепер нам не потрібно самим створювати конкретні об'єкти цих класів:
private FilmService filmService = new FilmServiceImpl();
Натомість можна позначити поле спеціальною анотацією і Spring сам підбере відповідну реалізацію:
@Autowired
private FilmService filmService;
Анотація @Autowired(автозв'язування) повідомляє Spring про те, що він має покопатися у себе в контексті і підставити сюди відповідний бін. Дуже зручно. Якщо раніше ми використовували інтерфейси, щоб не турбуватися щодо конкретної реалізації методів, то тепер нам не потрібно турбуватися навіть щодо реалізації самого інтерфейсу і навіть знати її назву. Ідея підказує, що використовувати автозв'язування на полі не рекомендується, краще використовувати конструктор чи сетер. Докладніше про це почитаємо в документації. Для нас, в принципі, це не важливо, можна сміливо залишати так. Але якщо вже ідея просить, то поважимо, щоб все було красиво і без будь-яких жовтих попереджень. У класі контролера створимо сетер і позначимо інструкцією його:
@Controller
public class FilmController {

    private FilmService filmService;

    @Autowired
    public void setFilmService(FilmService filmService) {
        this.filmService = filmService;
    }
І аналогічно робимо сетер для FilmDAOв класі FilmServiceImpl. Далі буде... Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 1) Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 2) Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 3) Знайомство з Maven, Spring, MySQL, Hibernate та перший CRUD додаток (частина 4)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ