Hibernate CRUD приложение с сервисным уровнем

В прошлом уроке мы построили Crud приложение, но это был не совсем правильный способ его построения. В этом уроке мы немного модернизируем наше CRUD приложение.

В прошлом уроке у нас было всего два уровня в нашем приложении, это:

  • Уровень контроллера, который принимает запросы пользователя и в котором должна была находиться вся какая-либо бизнес логика нашего приложения
  • и ДАО уровень, который исключительно для прямого взаимодействия с БД.
Search Icon

Но правильно чтобы бил еще один уровеньсервисный.

Давайте создадим папку service и создадим в ней сервисный класс.

Далее подробнее зачем он нужен.

В сервис классе внедряются все ДАО, которые есть в приложении. Таким образом объект сервиса объединяет все ДАО объекты и в контроллере можно будет работать с ДАО объектами через единственный объект сервиса.

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

Example

Тогда получается, что:

  • контроллер только получает и отправляет запросы,
  • вся бизнес-логика приложения в сервисе,
  • а в ДАО только чисто код для работы с БД.

То есть, как видим, обеспечиваться дополнительная модульность этой прослойкой.

Также еще очень полезный момент, что обеспечивается слабая связность между дао уровнем и уровнем контроллера.

Помним, что согласно DAO паттерну Dao классы нужны только для сокрытия низкоуровневого взаимодействия с ресурсом, в них не должно быть какой-либо сложной бизнес логики, поэтому если мы внедрим все DAO классы в контроллере и будем использовать их методы в сочетании с бизнес логикой без уровня сервиса, то это может вызвать ряд проблем.

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

Теперь пусть на месте ДАО метода, который мы хотим заменить в контроллере будет метод сервис класса и в этом сервис методе пусть вызывается ДАО метод. В этом сервис методе мы можем менять ДАО метод на любой какой нам вздумается. И в этом вся суть, что поменяв единожды ДАО метод в сервис методе он автоматически поменяется везде в контроллере и менять всё вручную в контроллере нам не придется, только ДАО метод в сервис методе.

Это и есть слабая связность между ДАО и контроллером.

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

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

Для начала, создаем интерфейс на основе которого будет создаваться сервис класс:

package com.MavenWebAps.hibernateCRUDap.service; import java.util.List; import com.MavenWebAps.hibernateCRUDap.entity.Actor; public interface ServiceInterface { public Actor getActor(int actorId); public void setActor(Actor actor); public List getListOfActors(); public void deleteActor(int actorid); }

Давайте же создадим этот сервис класс.

package com.MavenWebApps.hibernateCRUDap.service; import java.util.List; //Также как и @Controller и @Repositiry аннотация //@Service наследуется от @Component поэтому //спринг анализирует этот класс как особый бин. @Service //Объект (бин) этого класса-сервиса будет внедрен //в контроллере и через этот объект будем работать //с ДАО объектами. public class ServiceInterfaceImpl implements ServiceInterface { //внедряеться ДАО класса Актера. @Autowired private ActorDAO actorDAO; //На уровне ДАО убираем @Transactional //на уровне сервиса добавляем @Transactional. @Transactional //Метод для выборки актера из базы. public Actor getActor(int actorId) { //Здесь просто вызываем соответствующий //метод ДАО объекта. return actorDAO.getActor(actorId); } @Transactional //Метод для вставки актера в базу. public void setActor(Actor actor) { //Пример слабой связности о которой //говорилось ранее: допустим //в контроллере много где вызываеться //метод setActor этого сервис класса. //Если мы поменяем дао метод setActor //в этом методе на другой дао метод //то он автоматически меняеться везде //в контроллере где вызываеться setActor //сервис класса и в контроллере в каждом //месте использования метода setActor //сервис класса ничего менять не нужно //как если бы там вызывался //setActor ДАО класса. actorDAO.setActor(actor); } //Дальше также в сервисных методах вызываються //соответствующие методы ДАО класса. Как видим //здесь простой случай, поскольку приложение //у нас простое и из-за этого особой бизнес //логики у нас нет, потому просто вызываем //Дао методы в сервис методах. @Transactional public List getListOfActors() { return actorDAO.getListOfActors(); } @Transactional public void deleteActor(int actorid) { actorDAO.deleteActor(actorid); } }

Поправим контроллер. Уберем из него все ДАО методы и вместо них пусть будут сервис методы.

package com.MavenWebAps.hibernateCRUDap.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.MavenWebAps.hibernateCRUDap.entity.Actor; import com.MavenWebAps.hibernateCRUDap.service.ServiceInterface; import org.springframework.ui.Model; @Controller @RequestMapping(“/actor”) public class CRUDController { //Теперь вместо DAO объекта внедряем сервис //объект (бин) уже через методы которого //будет происходить работа с DAO объектом //для взаимодействия с БД. @Autowired private ServiceInterface sertviceObj; @RequestMapping(“/getListOfActors”) public String getListOfActors(Model model) { model.addAttribute(“listofactors”, //теперь используем sertviceObj sertviceObj.getListOfActors()); return “ListOfActorsJSP”; } @RequestMapping(“/showFormForUpdating”) public String updateActorsFor( @RequestParam(“actorId”) int actorid, Model model) { model.addAttribute(“someactor”, //теперь используем sertviceObj sertviceObj.getActor(actorid));; return “ActorJSPUpdateForm”; } @RequestMapping(“/showFormForSetting”) public String setActor(Model model) { model.addAttribute(“someactor”, new Actor()); return “ActorJSPNewActorForm”; } @RequestMapping(“/setActor”) public String setActor(@ModelAttribute(“someactor”) Actor actor, Model model){ //теперь используем sertviceObj sertviceObj.setActor(actor);; return “redirect:/actor/getListOfActors”; } @RequestMapping(“/deleteActor”) public String deleteActor( @RequestParam(“actorId”) int actorid, Model model) { //теперь используем sertviceObj sertviceObj.deleteActor(actorid);; return “redirect:/actor/getListOfActors”; } }

Также не забываем убрать Transactional в ДАО классе. Эти аннотации теперь в сервис классе.

package com.MavenWebAps.hibernateCRUDap.dao; import java.util.List; import javax.transaction.Transactional; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.MavenWebAps.hibernateCRUDap.entity.Actor; @Repository public class ActorDAOImpl implements ActorDAO { @Autowired private SessionFactory sessionFactory; //@Transactional public Actor getActor(int actorId) { Session session = sessionFactory.getCurrentSession(); return session.get(Actor.class, actorId); } //@Transactional public void setActor(Actor actor) { Session session = sessionFactory.getCurrentSession(); session.saveOrUpdate(actor); } //@Transactional public List getListOfActors() { Session session = sessionFactory.getCurrentSession(); return session.createQuery(“from Actor”, Actor.class).getResultList(); } //@Transactional public void deleteActor(int actorid) { Session session = sessionFactory.getCurrentSession(); session.createQuery( “delete from Actor where id=:actorid”) .setParameter(“actorid”, actorid).executeUpdate(); } }

Давайте же перейдем на главную страницу со списком актеров. Как мы помним, на эту страницу можно перейти через обработчик getListOfActors. Сделаем это:

Как видим, страница успешно открылась. Работает здесь всё аналогично предыдущему уроку, только теперь с сервисным уровнем.

Создание CRUD-приложения с Hibernate и Spring

В этом уроке создадим простое CRUD приложение с использованием hibernate.

Example

CRUD это аббревиатура:

  • C (Create)Добавить новые данные в БД.
  • R (Read)Прочитать данные из БД.
  • U (Update) Обновить данные в БД.
  • D (Delete)удалить данные из БД.

Наше приложение будет реализовывать все эти четыре операции.

Эти операции будут производиться с таблицей. Пускай это будет таблица "Актеры" с аттрибутами "Имя актера" и "Самый известный фильм".

Приложение будет выглядеть примерно так:

Как видим, здесь есть ссылка add actor, которая ведет на другую страницу на которой есть форма для добавления нового актера в таблицу с актерами. Это Create операция.

Рядом с каждым актером есть ссылка update, которая ведет на другую страницу на которой есть форма для обновления данных актера рядом с которым была нажата эта ссылка. Это, очевидно, Update операция.

Также рядом с каждым актером есть ссылка для удаления конкретного актера. Это, очевидно, Delete операция.

Read же операция, это само присутствие на странице списка актеров. То есть, чтобы отобразить на странице список актеров, нужно их сначала выбрать из БД, то есть совершить Read операцию.

Давайте для начала создадим таблицу actor, в которой будут храниться актеры. И сразу туда добавим пару актеров.

Создадим Мавен приложение с такой структурой и файлами:

В папке entity хранятся классы связанные с таблицами в БД с помощью hibernate. В данном случае будет всего один класс actor, который будет связан с таблицей с актерами.

В папке controller есть класс контроллер, который будет принимать запросы, перенаправлять на страницы и совершать CRUD операции через DAO объект, который находиться в папке dao.

Dao объект нужен для взаимодействия с БД. О нем подробнее далее.

Также можно увидеть три страницы jsp. ListOfActors отображает всех актеров из таблицы с актерами (это та страница, которую можно увидеть на картинке выше), страница ActorJSPNewActorForm на которой можно добавить нового актера в БД и страница ActorJSPUpdateForm на которой можно обновить актера в БД.

Теперь давайте добавим необходимые зависимости в pom файл, чтобы приложение работало.

4.0.0 com.MavenWebApps hibernateCRUDapp jar 0.0.1-SNAPSHOT hibernateCRUDapp Maven Webapp http://maven.apache.org org.springframework spring-webmvc 5.3.0 org.springframework spring-tx 5.3.0 org.springframework spring-orm 5.3.0 org.hibernate hibernate-core 5.3.26.Final mysql mysql-connector-java 8.0.32 com.mchange c3p0 0.9.5.5 javax.servlet javax.servlet-api 3.1.0 javax.servlet jstl 1.2 junit junit 3.8.1 test org.apache.maven.plugins maven-war-plugin 3.4.0 false

Как мы помним, в обычном hibernate приложении подключение к базе настраивается в файле hibernate.cfg.xml.

В MVC же приложении подключение настраивается в applicationContext.xml.

Здесь же настраивается и бин SessionFactory, то есть создавать объект SessionFactory java кодом, как в обычном hibernate приложении не придется, создавать новые объекты сессий можно будет через заранее сконфигурированный в applicationContext бин.

org.hibernate.dialect.MySQLDialect true

Создадим класс ранее созданной таблицы actor. Здесь ничего нового.

package com.MavenWebAps.hibernateCRUDap.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity @Table(name = “actor”) public class Actor { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = “id”) private int id; @Column(name = “name”) private String actorName; @Column(name = “most_famous_film”) private String film; public Actor() { } public Actor(String actorName, String film) { this.actorName = actorName; this.film = film; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getActorName() { return actorName; } public void setActorName(String actorName) { this.actorName = actorName; } public String getFilm() { return film; } public void setFilm(String film) { this.film = film; } @Override public String toString() { return “Actor [id=” + id + “, actorName=” + actorName + “, film=” + film + “]”; } }

Давайте для начала создадим интерфейс на основе которого будем создавать DAO класс.

package com.MavenWebAps.hibernateCRUDap.dao; import java.util.List; public interface ActorDAO { public Actor getActor(int actorId); public void setActor(Actor actor); public List getListOfActors(); public void deleteActor(int actorid); }

Через DAO бин, который будет внедряться в контроллер, будут производиться CRUD операции с БД.

В DAO классе ниже можно увидеть методы с помощью которых происходит непосредственное взаимодействие с БД с помощью hiberante.

Также важно отметить, что DAO бин, это особенный бин, класс которого помечается аннотацией @Repository.

package com.MavenWebAps.hibernateCRUD.dao; import java.util.List; //Также как и @Controller аннотация @Repository //наследуется от @Component, потому спринг //анализирует этот класс как особый бин. //Этой аннотацией помечется DAO объект. //Спринг распознает объект помеченный этой аннотацией //как дао объект и благодаря этому //в нем можно использовать @Transactional. @Repository //Помните паттерн ДАО. Где все низкоуровневое //взаимодействие с ресурсом (в нашем случае с БД) //скрывалось в дополнительном классе. В Spring MVC он //используется для сокрытия низкоуровневого //взаимодействия с базой с помощью hibernate. //В этом классе мы создаем методы для взаимодействия //с базой. В конторллере же мы используем эти методы, //то есть никакого hibernate кода в контроллере нет //и не должно быть. public class ActorDAOImpl implements ActorDAO { @Autowired //Внедряем sessionFactory (такой же id имеет бин фабрики //сессий который мы конфигурировали в applicationContext). private SessionFactory sessionFactory; //READ //Ниже метод для выборки актера из базы по id. //Все что выполняется в методе помеченном @Transactional //выполняется в отдельной транзакции. То есть это просто //некоторое упрощение чтобы нам не приходилось писать //session.beginTransaction(); и //session.getTransaction().commit();. @Transactional public Actor getActor(int actorId) { Session session = sessionFactory.getCurrentSession(); return session.get(Actor.class, actorId); } //CREATE, UPDATE //метод для вставки актера в базу ли обновления //актера в базе. @Transactional public void setActor(Actor actor) { Session session = sessionFactory.getCurrentSession(); //Есть довольно удобный метод saveOrUpdate, который //будет смотреть на id присланного объекта actor. //Если актер с таким id уже есть в таблице с актерами //то этот метод обновляет строку таблицы с таким id, //а если такого нет то добавляет присланный объект //в таблицу. session.saveOrUpdate(actor); } //Тоже READ только всех актеров, //а не одного как метод getActor. @Transactional public List getListOfActors() { Session session = sessionFactory.getCurrentSession(); return session.createQuery(“from Actor”, Actor.class).getResultList(); } //DELETE //метод для удаления актера из базы по id @Transactional public void deleteActor(int actorid) { Session session = sessionFactory.getCurrentSession(); session.createQuery( “delete from Actor where id=:actorid”) .setParameter(“actorid”, actorid).executeUpdate(); } }

Теперь создадим контроллер, который будет перенаправлять на страницы и совершать CRUD операции через DAO бин, который внедряется в контроллер.

package com.MavenWebApps.hibernateCRUD.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.MavenWebApps.hibernateCRUD.dao.ActorDAO; import com.MavenWebApps.hibernateCRUD.entity.Actor; import java.util.List; @Controller @RequestMapping(“/actor”) public class CRUDController { //Внедряем созданный DAO объект (бин) чтобы //через него взаимодействовать с базой. @Autowired private ActorDAO actorDao; //READ //Как можно увидеть этот обработчик выбирает //из БД всех актеров через DAO объект //и перенаправляет их в аттрибуте //listofactors на страницу ListOfActors.jsp //где все актеры из аттрибута будут отображены. @RequestMapping(“/getListofActors”) public String getListofActors(Model model) { model.addAttribute(“listofactors”, actorDao.getListofActors()); return “ListofActorsJSP”; } //Этот обработчик перенаправляет на страницу //ActorJSPUpdateForm.jsp на которой можно //совершить UPDATE актера. Как видим в обработчик //передаеться id актера, по этому id выбираеться //из БД актер и помещаеться в аттрибут someactor //и данные этого актера из этого аттрибута будут //отображаться на странице ActorJSPUpdateForm.jsp. //Это просто нужно чтобы при обновлении данных //актера мы видели на странице и предидущие //данные обновляемого актера. @RequestMapping(“/showFormForUpdating”) public String showFormForUpdating( @RequestParam(“actorId”) int actorId, Model model) { model.addAttribute(“someactor”, actorDao.getActor(actorId)); return “ActorJSPUpdateForm”; } //Этот обработчик перенаправляет на страницу //ActorJSPNewActorForm.jsp на которой можно //совершить CREATE актера. То есть создать //нового актера и поместить в таблицу с актерами. //Как видим здесь создаеться новый обьект актера //и помещаеться в аттребут someactor, который //будет доступен на странице //ActorJSPNewActorForm.jsp. Там этот обьект будет //заполняться через форму и добавляться в БД. @RequestMapping(“/showFormForSetting”) public String setActor(Model model) { model.addAttribute(“someactor”, new Actor()); return “ActorJSPNewActorForm”; } //CREATE, UPDATE //Этот обработчик просто добавляет актера //в таблицу или обновляет актера в таблице //с актерами и перенаправляет обратно //на страницу со списком актеров через //обработчик getListofActors. @RequestMapping(“/setActor”) public String setActor(@ModelAttribute(“someactor”) Actor actor, Model model){ actorDao.setActor(actor); //ВАЖНЫЙ момент чтобы перейти в какой-то //обработчик контроллера, а не на какую-то //страницу пишем redirect:… return “redirect:/actor/getListofActors”; } //DELETE //Этот обработчик удаляет актера из таблицы //с актерами и перенаправляет обратно //на страницу со списком актеров через //обработчик getListofActors. @RequestMapping(“/deleteActor”) public String deleteActor( @RequestParam(“actorId”) int actorid, Model model) { actorDao.deleteActor(actorid); return “redirect:/actor/getListofActors”; } }

Создадим главную страницу на которой будут отображаться все актеры из таблицы actor, и даются ссылки для create, updete, delete актеров.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ page isELIgnored="false" %> CRUD JSP
${singleActor.actorName} ${singleActor.film} update / delete
add actor

Теперь создадим страницу с формой для добавления нового актера в таблицу актеров.

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ page isELIgnored="false"%> Create JSP Name:
Most famous film:

Теперь создадим страницу с формой для обновления данных существующего актера.

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ page isELIgnored="false"%> Update JSP Old Info: Old name: ${someactor.actorName}, Old film: ${someactor.film} New Info:
Name:
Most famous film:

Давайте же перейдем на главную страницу со списком актеров. Как мы помним, на эту страницу можно перейти через обработчик getListOfActors. Сделаем это:

Как видим, обработчик getListOfActors успешно выбрал актеров из БД и они успешно вывелись на странице.

Давайте теперь добавим актера в БД. Для этого нажмем на add actor. В результате этого произойдет переход в обработчик showFormForSetting, который создает новый объект актера, который будет заполняться формой на странице на которую переводит этот обработчик. Ниже эта страница. Заполним форму новым  актером и нажмем submit.

По нажатию на submit происходит вызов обработчика setActor, который добавляет актера в БД и перенаправляет в обработчик getListOfActors, который перенаправляет обратно на главную страницу со всеми актерами.

Как видим, новый актер успешно был добавлен в БД и отображен на странице.

Теперь давайте нажмем на Update рядом с актером Johnny Depp, чтобы можно было обновить данные этого актера. По нажатию происходит вызов обработчика showFormForUpdating, который перенаправляет на страницу для обновления данных актера. Как можно увидеть, этот обработчик успешно выбрал из БД текущие данные актера Johnny Depp, которые впоследствии были отображены на странице.

Объект актера Johnny Depp извлеченный обработчиком был связан с формой для обновления данных актера. Давайте изменим этой формой название фильма и нажмем submit.

По нажатию на submit происходит вызов обработчика setActor, который обновляет данные актера в БД и перенаправляет в обработчик getListOfActors, который перенаправляет обратно на главную страницу со всеми актерами.

Как видим, данные актера Johnny Depp, а именно название фильма, успешно обновились на странице, а значит и в БД.

Давайте теперь удалим из БД последний фильм, для этого просто нажмем Delete напротив актера Jeffrey Dean Morgan.

Как видим, актер успешно удалился на странице, а значит и в БД.