В прошлом уроке мы построили Crud приложение, но это был не совсем правильный способ его построения. В этом уроке мы немного модернизируем наше CRUD приложение.
В прошлом уроке у нас было всего два уровня в нашем приложении, это:
Уровень контроллера, который принимает запросы пользователя и в котором должна была находиться вся какая-либо бизнес логика нашего приложения
и ДАО уровень, который исключительно для прямого взаимодействия с БД.
Но правильно чтобы бил еще один уровень – сервисный.
Давайте создадим папку service и создадим в ней сервисный класс.
Далее подробнее зачем он нужен.
В сервис классе внедряются все ДАО, которые есть в приложении. Таким образом объект сервиса объединяет все ДАО объекты и в контроллере можно будет работать с ДАО объектами через единственный объект сервиса.
Также важный момент, что вся бизнес логика приложения больше не будет находиться на уровне контроллера, как в прошлой версии приложения, она теперь будет располагаться на уровне сервиса.
Тогда получается, что:
контроллер только получает и отправляет запросы,
вся бизнес-логика приложения в сервисе,
а в ДАО только чисто код для работы с БД.
То есть, как видим, обеспечиваться дополнительная модульность этой прослойкой.
Также еще очень полезный момент, что обеспечивается слабая связность между дао уровнем и уровнем контроллера.
Помним, что согласно 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.
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 файл, чтобы приложение работало.
Как мы помним, в обычном 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 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.
Как видим, актер успешно удалился на странице, а значит и в БД.
Кроме основного мавен репозитория из которого происходит загрузка зависимостей также есть и другие репозитории, и чтобы скачивание происходило и оттуда его нужно указать в pom.xml.
Давайте удалим файл web.xml, чтобы теперь настраивать приложение в Java классе MySpringMvcDispServlInit вместо web.xml. Также добавим Java класс для настройки бинов вместо applicationContext.xml.
Также создадим сконфигурированную нами ранее дополнительную папку ресурсов resourcedir.
Заметьте, что директории в папке java названы как groupid и artifactid в pom файле, а после папки с названием artifactid (в нашем случае firstMavenWebApp), уже специфические папки с исходниками – config, controller и т.д. Так желательно называть, как говорилось ранее.
Также, как видим, создана страница somePage, к которой будет происходить переход через SimpleController.java.
Применение изменений в pom файле
Чтобы изменения в pom файле вступили в силу в нашем проекте, то есть скачались и добавились в наш проект все зависимости, плагины и применились другие настройки сборки, нужно нажать:
Начнется скачивание зависимостей из интернета. После этого они добавятся в наш проект и мы сможем ими смело пользоваться при программировании.
Настройка DispatcherServlet
Давайте настроим Java версию web.xml. Настроим DispatcherServlet подобным образом, как мы это делали в web.xml:
package com.mavenwebapps.firstMavenWebAp.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
//Этот класс вместо web.xml. Здесь конфигурируем
//DispatcherServlet как мы это делали в web.xml
//только Java кодом.
public class MySpringMvcDispServlInit
extends AbstractAnnotationConfigDispatcherServletInitializer {
//Для этого переопределяем методы класса с названием
//AbstractAnnotationConfigDispatcherServletInitializer.
//Пишем вот такой метод)
@Override
protected Class>[] getRootConfigClasses() {
return null;
}
@Override
protected Class>[] getServletConfigClasses() {
//Помним что в web.xml мы указывали параметром
//сервлету имя файла конфигурации бинов.
//Здесь тоже передаем имя этого файла
//только в данном случае как показано ниже.
return new Class>[] { JavaSpringConfig.class };
}
@Override
protected String[] getServletMappings() {
//И также как и в web.xml задаем путь по которому
//происходит переход к DispatcherServlet.
//”/” значит по корневому пути, то есть по пути
//http://localhost:8080/firstMavenWebProject/
return new String[] { “/” };
}
}
Настройка applicationContext
Давайте теперь в файлике для настройки спринг, как мы уже делали это раньше, настроим путь к директории, в которой будут искаться классы с аннотациями по которым будут создаваться бины, то есть путь com.MavenWebAps.firstMavenWebAp.
Здесь же настроим путь к папке с View. Это мы уже тоже делали раньше.
package com.MavenWebAps.firstMavenWebAp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
//Это класс конфигурации спринг бинов
//вместо applicationContext.xml.
@Configuration
@EnableWebMvc
@ComponentScan(basePackages =”com.MavenWebAps.firstMavenWebAp”)
public class JavaSpringConfig {
//Здесь также как мы делали в xml создаем бин,
//который определяет путь к вьюхам.
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver =
new InternalResourceViewResolver();
viewResolver.setPrefix(“/WEB-INF/jsp/”);
viewResolver.setSuffix(“.jsp”);
return viewResolver;
}
}
//То есть в нашем проекте не осталось xml ВООБЩЕ.
Настройка контроллера
Создадим контроллер, в котором определим, что по корневому пути, то есть по http://localhost:8080/firstMavenWebAp/, будет происходить переход к somePage.jsp
package com.MavenWebApps.firstMavenWebApp.controller;
import org.springframework.stereotype.Controller;
@Controller
public class SimpleController {
@GetMapping(“/”)
public String goSomePage() {
return “somePage”;
}
}
Проверка работы приложения
После этого запускаем приложение стандартным образом и видим содержимое somePage.jsp:
Далее создаем Мавен проект. Для этого переходим по пути:
File > new > Maven Project
В открывшемся окне нажимаем Next.
Далее необходимо найти и выбрать archetype нашего проекта, то есть его тип. В нашем случае нам нужен самый простой maven веб-проект. Он называется maven-archetype-webapp.
Ищем его:
Теперь нужно указать GroupId — это, условно говоря, имя нашей компании или организации — и ArtifactId, то есть имя самого проекта. Подробно о том, что это такое и зачем нужно, поговорим чуть позже.
После нажатия Finish начнется создание проекта.
Maven будет подгружать из интернета всё необходимое по ходу создания проекта.
Ниже можно увидеть созданный проект:
Сразу после создания проекта в нем может не присутствовать папка java. В таком случае ее можно создать вручную.
До этого момента мы все jar файлы наших проектов скачивали и добавляли вручную в папку lib проекта.
Чтобы не делать это вручную существует Maven – средство для управления сборкой проекта и добавления зависимостей.
Зависимость – это какой-либо jar от которого зависит наш проект. Мы делаем запрос на добавление какого-либо jar файла в проект и он добавляется автоматически средствами Maven.
Добавления jar файлов происходит следующим образом:
Пишем в конфигурационном файле какие jar файлы нужны нам в проекте
Maven проверяет не скачивались ли эти jar ранее, и если скачивались, то извлекает их из локального репозитория и добавляет их в проект
Если они не скачивались то Maven автоматически скачивает их из интернета в локальный репозиторий и добавляет их в проект.
В windows все скачанные jar файлы сохраняются в локальном репозитории по пути С:\Users\<user-homedir-name>\.m2\repository
Структура Maven веб-проекта
Мавен определяет стандартную структуру веб проекта:
В папке java храним все наши классы исходники.
В папке webapp все веб ресурсы (папка WEB-INF, картинки и т.д.).
В папке resourcesразличные конф файлы.
В папке target мавен будет сохранять результаты сборки и другое.
Также может быть папка test с юнит тестами.
Конф. файл pom.xml, в котором пишутся зависимости, которые maven должен скачать всегда в корне этой структуры.
pom файл. Maven проект
Огромное преимущество Мавен в том, что созданный Мавен проект в одной IDE можно с легкостью открыть в другой.
То есть Мавен проект не зависит от IDE.
pom файл состоит из 3-х частей:
project meta data – какие либо данные о проекте(имя, версия…),
список зависимостей для скачивания,
и плагины – добавление каких-то доп. задач для запуска.
Структура Maven-проекта может отличаться, так как у разных проектов бывают разные цели — это может быть веб-приложение, консольная программа или что-то другое. Соответственно, и структура файлов и папок будет разной.
Здесь на помощь приходитMaven archetype — это шаблон (тип) Maven-проекта. При создании нового проекта можно выбрать нужный archetype, и Maven автоматически сгенерирует базовую структуру: необходимые директории и стартовые файлы, соответствующие выбранному типу проекта.
Таких архитипов на самом деле много и нужный можно найти при cоздании приложения в IDE
Теперь настроим в нашем приложении связь Многие-ко-Многим.
Пусть теперь в нашем приложении будет еще одна новая таблица – "Издательство".
Эта таблица будет связана с ранее созданной таблицей "Книги автора".
Любая книжка может быть напечатана несколькими издательствами и любое издательство может печатать какие-угодно книги.
То есть связь многие книги ко многим издательствам.
Ясное дело, при удалении издательства из таблицы publisherне должны удаляться связанные с этим издательством книги в таблице author_books, поскольку эти книги могут быть связаны и с другими издательствами.
И наоборот тоже – удаление книги не должно приводить к удалению связанных с ней издательств.
Поэтому и в классе таблицы publisher и в классе таблицы author_booksнеобходимо исключить CascadeType=REMOVE.
Сделаем это.
Создадим теперь с помощью sql запроса таблицу publisherв которой будут храниться издательства.
Кто знает базы данных должен знать, что связь многие ко многим между двумя таблицами реализуется с помощью промежуточной таблицы, где есть два столбца – id издательства и id книги. Создадим эту таблицу.
Теперь создадим класс созданной только что таблицы publisher. И настроим связь Многие-ко-Многиммежду этим классом и классом AuthorBooks через промежуточную таблицу.
package HibernateApps;
import java.util.ArrayList;
import javax.persistence.*;
@Entity
@Table(name = “publisher”)
public class Publisher {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name=”id”)
private int id;
@Column(name=”name”)
private String publisherName;
//Теперь используем аннотацию
//@ManyToMany (Многие-ко-многим).
//многие Publisher ко многим AuthorBooks.
//Важно убрать CascadeType.REMOVE чтобы
//при удалении из БД какой-то из книги
//не удалялись из БД издательства
@ManyToMany(cascade = {CascadeType.DETACH,
CascadeType.REFRESH,
CascadeType.PERSIST,
CascadeType.MERGE})
//Теперь настраиваем связи таблицы publisher
//с таблицей author_books через промежуточную
//таблицу author_books_publisher.
@JoinTable(
Имя промежуточной таблицы.
name = “authours_books_publisher”,
//Указываем название атрибута
//промежуточной таблицы в котором
//хранятся идентификаторы строк
//из таблицы publisher.
joinColumns=@JoinColumn(name=”publisher_id”),
//Указываем название атрибута
//промежуточной таблицы в котором
//хранятся идентификаторы строк
//из таблицы author_books.
inverseJoinColumns=@JoinColumn(name=”authorbook_id”))
private List authorbook;
public Publisher() {
}
public Publisher(String publisherName) {
this.publisherName = publisherName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPublisherName() {
return publisherName;
}
public void setPublisherName(
String publisherName) {
this.publisherName = publisherName;
}
public List getAuthorbook() {
return authorbook;
}
public void setAuthorbook(
List authorbook) {
this.authorbook = authorbook;
}
//Для связывания объекта книги
//с объектом издательства просто
//добавляем объект книги в List
//объекта издательства. Тогда при
//добавлении в БД издательства, через
//объект издательства добавляются в БД
//и книги находящиеся в List
//в этом объекте издательства.
public void setOneBook(
AuthorBooks authorbook){
if (this.authorbook==null) {
this.authorbook = new ArrayList<>();
}
this.authorbook.add(authorbook);
}
@Override
public String toString() {
return “Publisher [id=” + id
+ “, publisherName=”
+ publisherName
+ “, authorbook=”
+ authorbook + “]”;
}
}
Теперь в классе таблицы author_books настроим связь Многие-ко-Многиммежду этим классом и классом Publisher также через промежуточную таблицу.
Связь настраивается таким же образом как и в классе Publisher, только меняем местами publisher_id и authorbook_id
package HibernateApps;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = “author_books”)
public class AuthorBooks {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
@Column(name=”id”)
private int id;
@Column(name=”name”)
private String bookName;
@ManyToOne(cascade ={CascadeType.DETACH,
CascadeType.REFRESH,
CascadeType.PERSIST,
CascadeType.MERGE})
@JoinColumn(name=”author_id”)
private Author author;
//Также используем аннотацию
//@ManyToMany (Многие-ко-Многим).
//Многие AuthorBooks ко многим Publisher.
//Важно убрать CascadeType.REMOVE чтобы
//при удалении из БД какого-то из издательств
//не удалялись из БД книги напечатанные им.
@ManyToMany(cascade ={CascadeType.DETACH,
CascadeType.REFRESH,
CascadeType.PERSIST,
CascadeType.MERGE})
@JoinTable(name=”authorbooks_publisher”,
//Связь с таблицей publisher
//через промежуточную таблицу
//в этом классе настраиваем также
//как и в классе Publisher только
//как можно увидеть ниже
//в joinColumns теперь authorbook_id,
//а в inverseJoinColumns
//теперь publisher_id.
joinColumns=
@JoinColumn(name=”authorbook_id”),
inverseJoinColumns=
@JoinColumn(name=”publisher_id”))
private List publisher;
public AuthorBooks () {
}
public AuthorBooks(String bookName) {
this.bookName = bookName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
@Override
public String toString() {
return “Book [id=”
+ id + “, bookName=”
+ bookName + “]”;
}
}
Пример программы со связью Многие-ко-Многим:
package HibernateApps;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
//Добавим Publisher в sessionfactory
SessionFactory sessionFactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Author.class)
.addAnnotatedClass(AuthorInfo.class)
.addAnnotatedClass(AuthorBooks.class)
.addAnnotatedClass(Publisher.class)
.buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
try {
session.beginTransaction();
//создаем издательства
Publisher publisher = new Publisher(“Yakaboo”);
Publisher publisher1 = new Publisher(“Vivat”);
Publisher publisher2 = new Publisher(“IPIO”);
//извлем три книги из БД
AuthorBooks authorbook =
session.get(AuthorBooks.class,1);
AuthorBooks authorbook1 =
session.get(AuthorBooks.class,2);
AuthorBooks authorbook2 =
session.get(AuthorBooks.class,3);
//Связываем первое издательство
//с первой и третьей книгой.
publisher.setOneBook(authorbook);
publisher.setOneBook(authorbook2);
//Связываем второе издательство
//с первой, второй и третьей книгой.
publisher1.setOneBook(authorbook);
publisher1.setOneBook(authorbook1);
publisher1.setOneBook(authorbook2);
//Связываем третье издательство
//со второй и третьей книгой.
publisher2.setOneBook(authorbook1);
publisher2.setOneBook(authorbook2);
//сохраняем издательства в базу
session.save(publisher);
session.save(publisher1);
session.save(publisher2);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
} finally{
session.close();
}
}
}
Давайте запустим нашу программу.
Как видим, сначала произошло три select запроса на выборку книг (как мы помним, мы извлекали из БД три книги с помощью get).
Далее произошла вставка (insert) трех издательств в таблицу publisher и потом произошло связывание издательств и книжек посредством вставки идентификаторов в промежуточную таблицу.
Как видим, вставка издательств в таблицу publisherпроизошла успешно. Можно убедиться, что в промежуточной таблице книги и издательства связаны в соответствии с тем как мы их связывали в программе
package HibernateApps;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
//Этот класс с таблицы author
@Table(name = “author”)
public class Author {
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY
)
private int id;
@Column(name=”name”)
private String authorName;
@OneToOne(cascade = {CascadeType.ALL})
@JoinColumn(name=”author_info_id”)
private AuthorInfo authorInfo;
// LAZY, EAGER – это типы извлечения данных.
// Если мы извлекаем автора из БД в объект Author
// то извлекаются из БД и его authorbooks.
// В прошлом уроке мы видели как это работает.
// В этом же уроке мы рассмотрим настройку
// того КОГДА извлечение книг будет происходить.
// При настроеном EAGER книги автора извлекаються
// из БД в список authorbooks, который
// в объекте Author СРАЗУ как только мы извлекли
// в этот объект Author автора из базы,
// но очевидно что это плохо может сказаться
// на производительности так как что если мы
// хотим в конкретной ситуации извлечь
// только автора, а связанные с ним строки в других
// таблицах, такие как его книги, пока не извлекать
// поскольку в этой конкретной ситуации они нам
// пока не нужны.
// Решение это LAZY. Здесь все просто.
// Данные связанных с автором таблиц извлекаются
// только тогда когда они нам нужны. То есть при извлечении
// автора из базы извлекается только автор,
// а связанные с ним книжки нет, они извлекуться
// и добавяться в List объекта извлеченного автора
// только когда у этого объекта будет вызван
// метод getAuthorBooks() либо любой другой метод,
// в котором будет использоваться List с книгами.
// При OneToMany и ManyToMany по умолчанию
// стоит LAZY, при других EAGER.
//Сначала приведем пример с EAGER.
@OneToMany(fetch=FetchType.EAGER,
mappedBy=”author”,
cascade = CascadeType.ALL)
private List authorbooks;
public Author() {
}
public Author(String authorName) {
this.authorName = authorName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(
String authorName) {
this.authorName = authorName;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
public void setAuthorInfo(
AuthorInfo authorInfo) {
this.authorInfo = authorInfo;
}
//добавляем геттер сеттер для списка
public List getAuthorBooks(){
return authorbooks;
}
public void setAuthorBooks(
List authorbooks){
this.authorbooks = authorbooks;
}
@Override
public String toString() {
return “Author [id=” + id
+ “, authorName=”
+ authorName
+ “, authorInfo=”
+ authorInfo
+ “, authorbooks=”
+ authorbooks + “]”;
}
}
Пример программы с EAGER типом извлечения данных:
package HibernateApps;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
SessionFactory sessionFactory =
new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Author.class)
.addAnnotatedClass(AuthorInfo.class)
.addAnnotatedClass(AuthorBooks.class)
.buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
try {
session.beginTransaction();
//Здесь мы выбираем из БД автора
//и как говорилось данные связанные
//с этим автором в других таблицах
//загружаються из БД в обьект author
//СРАЗУ поскольку настроено EAGER.
Author author = session.get(Author.class,1);
//Обьект уже author содержит все книги
//связанные с автором.
//То есть они извлеклись из БД
//СРАЗУ как только мы вызвали get.
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
Давайте запустим нашу программу.
Как видим, по вызову метода getпроизошел запрос select, который извлекает не только автора из БД, а и его книги благодаря тому, что тип извлечения книг был установлен в EAGER.
Настройка типа извлечения данных.
Теперь изменим тип извлечения данных на LAZY.
package HibernateApps;
import java.util.List;
import javax.persistence.*;
@Entity
//это класс с таблицы author
@Table(name = “author”)
public class Author {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
@Column(name=”id”)
private int id;
@Column(name=”name”)
private String authorName;
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name=”author_info_id”)
private AuthorInfo authorInfo;
//Теперь приведем пример с LAZY.
@OneToMany(fetch=FetchType.LAZY,
mappedBy=”author”,
cascade = CascadeType.ALL)
private List authorbooks;
public Author() {
}
public Author(String authorName) {
this.authorName = authorName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(
String authorName) {
this.authorName = authorName;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
public void setAuthorInfo(
AuthorInfo authorInfo) {
this.authorInfo = authorInfo;
}
//добавляем геттер сеттер для списка
public List getAuthorBooks(){
return authorbooks;
}
public void setAuthorBooks(
List authorbooks){
this.authorbooks = authorbooks;
}
@Override
public String toString() {
return “Author [id=” + id
+ “, authorName=”
+ authorName
+ “, authorInfo=”
+ authorInfo
+ “, authorbooks=”
+ authorbooks + “]”;
}
}
Пример программы с LAZY типом извлечения данных:
package HibernateApp;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
SessionFactory sessionFactory =
new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(AuthorInfo.class)
.addAnnotatedClass(AuthorBooks.class)
.buildSessionFactory();
Session session =
sessionFactory.getCurrentSession();
try {
session.beginTransaction();
//Здесь мы выбираем из БД автора
//и как говорилось данные связанные
//с этим автором в других таблицах
//загружаються из БД в объект author
//НЕ СРАЗУ при вызове get поскольку
//используеться LAZY.
Author author=session.get(Author.class,1);
//Объект author пока НЕ содержит книги
//связанные с извлеченным только
//что автором. То есть они ПОКА
//не извлеклись из БД.
//Давайте теперь извлечем книги из БД
//в объект вызвав метод, в котором
//используеться List, в котором должны
//храниться книги автора.
author.getAuthorBooks();
//Теперь объект автора содержит связанные
//с ним в БД книги.
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
Давайте запустим нашу программу.
Как видим, по вызову метода getпроизошел запрос select, который не извлекает книги из БД, а только автора (ну на самом деле на скриншоте еще можно увидеть, что он также извлекает и из таблицы author_info из-за того, что по умолчанию у связей OneToOne стоит тип извлечения данных EAGER).
Это поведение стало возможным благодаря тому, что для коллекции книг был установлен тип извлечения LAZY.
Также на скриншоте видно, что произошёл второй запрос. Он был выполнен при вызове метода getAuthorBooks(). Именно тогда был выполнен дополнительный select, который извлёк книги из базы данных и поместил их в список внутри объекта автора.
Теперь настроим в нашем приложении связь 1-ко-Многим.
Создадим еще одну таблицу "Книги авторов", которая будет связана с таблицей author. В одной таблице хранятся авторы, а в другой их книги.
У каждого автора много книг, но у каждой книги только один автор. То есть связь – один автор ко многим книгам.
При удалении автора должны удаляться и его книги из другой таблицы.При удалении книги его автор не должен удаляться. Сделаем это.
Создадим теперь с помощью sql запроса таблицу author_books, в которой будут храниться книги авторов и которая будет связана с таблицей author с помощью внешнего ключа author_id.
Теперь создадим класс созданной только что таблицы author_books. И настроим теперь связь между этим классом и классом Author
Теперь в классе таблицы author настраиваем обратную связь с новым классом.
package HibernateApps;
import java.util.List;
@Entity
//это класс с таблицы author
@Table (name = “author”)
public class Author {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
@Column(name=”id”)
private int id;
@Column(name=”name”)
private String authorName;
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn(name=”author_info_id”)
private AuthorInfo authorInfo;
@OneToMany(fetch=FetchType.EAGER,
//О том что такое EAGER
//в следующем уроке.
mappedBy=”author”,
//Пускай будут включены
//все типы каскадных
//операций – CascadeType.ALL.
//При удалении
//автора пусть удаляются
//из БД и все его книги.
cascade = CascadeType.ALL)
//как видим многие книжки автора
//будут записываться в список.
private List authorbooks;
public Author() {
}
public Author(String authorName) {
this.authorName = authorName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(
String authorName) {
this.authorName = authorName;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
public void setAuthorInfo(
AuthorInfo authorInfo) {
this.authorInfo = authorInfo;
}
//добавляем геттер сеттер для списка
public List getAuthorBooks(){
return authorbooks;
}
public void setAuthorBooks(
List authorbooks){
this.authorbooks = authorbooks;
}
@Override
public String toString() {
return “Author [id=” + id
+ “, authorName=”
+ authorName
+ “, authorInfo=”
+ authorInfo
+ “, authorbooks=”
+ authorbooks + “]”;
}
}
В программе же создадим объект автора и три объекта книг, свяжем эти книги с объектом автора, и добавим эти все объекты в БД.
Потом извлечем из БД автора в объект автора. Вместе с автором также должны извлечься и связанные с ним книги,которые сохраняться в объекте автора.
Далее удалим одну книгу из БД через объект книги, который храниться в объекте автора. При этом связанный с этой книгой автор не должен удалиться из БД.
package HibernateApps;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
//Добавим AuthorBooks в sessionFactory.
SessionFactory sessionFactory =
new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Author.class)
.addAnnotatedClass(AuthorInfo.class)
.addAnnotatedClass(AuthorBooks.class)
.buildSessionFactory();
Session session =
sessionFactory.getCurrentSession();
try {
session.beginTransaction();
//создадим объект Author
Author author = new Author(“JJ Rouling”);
//и добавим его в таблицу author
session.save(author);
//Теперь создадим три объекта книги.
//Это книги автора, которого мы только
//что добавили в БД.
AuthorBooks authorbook =
new AuthorBooks(“Harry Potter 1”);;
AuthorBooks authorbook1 =
new AuthorBooks(“Harry Potter 2”);
AuthorBooks authorbook2 =
new AuthorBooks(“Harry Potter 3”);
//поэтому связываем автора с книгами
authorbook.setAuthor(author);
authorbook1.setAuthor(author);
authorbook2.setAuthor(author);
//сохраняем книги в базу
session.save(authorbook);
session.save(authorbook1);
session.save(authorbook2);
//Закоммитим и начнем
//новую сессию и транзакцию.
session.getTransaction().commit();
session=sessionFactory.getCurrentSession();
session.beginTransaction();
//Извлечем только что добавленного в БД
//автора из БД. Вместе с ним извлекуться
//связанные с ним книги и будут храниться
//в объекте автора author1.
Author author1 = session.get(Author.class, 1);
//Можем их увидеть в консоли.
System.out.println(author1);
//Удалим первую книгу автора из БД.
//При удалении книги из БД автор
//этой книги из БД не удаляется
//как и было задумано.
session.delete(
author1.getAuthorBooks().get(0));
//Также удалить нужно первую книгу
//и из списка книг в объекте author1.
author1.getAuthorBooks().remove(0);
//Извлечем автора из БД уже с двумя
//книгами.
Author author2 =
session.get(Author.class, 1);
//Удалим теперь автора из БД.
//При удалении автора из БД
//удаляються и его книги из БД
//как и было задумано.
session.delete(author2);
//Извлечем автора из БД еще раз
//Не должно получиться так как мы
//его только что удалили из БД.
Author author3 =
session.get(Author.class, 1);
session.getTransaction().commit();
//Объект author2 должен содержать
//две книги после удаления одной из БД.
//Проверим это.
System.out.println(author2);
//Объект author3 должен быть пуст
//после удаления его из БД.
//Проверим это.
System.out.println(author3);
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
И потом удалим автора из БД через объект автора. При этом связанные с этим автором книги тоже должны удалиться из БД.
Давайте запустим нашу программу.
Как видим, произошла вставка в БД четырех объектов, то есть автора и его книг.
После извлечения автора из БД можно увидеть в консоли, что он содержит в себе все книги, которые связаны с ним в БД.
После этого происходит запрос на удалениеодной книги из БД через объект книги, который хранился в author1.
Далее можно увидеть, что происходят запросы на удаление из БД остальных книг вместе с автором через объект author2.
После этого можно увидеть содержимое объекта author2. Можно увидеть, что в нем нет одной книги, она была удалена из БД. То есть удаление одной книги автора из БД через объектauthor1 прошло успешно.
После этого можно увидеть содержимое объекта author3. В нем null поскольку все книги вместе с автором уже были удалены из БД через объект author2.
Связь между объектами таблиц была односторонняя, то есть через объект Author удалялся из БД или сохранялся в БД связанный объект AuthorInfo.
Теперь сделаем наоборот. Так, чтобы через объект AuthorInfo производились какие-то действия в БД со связанным объектом Author.
Теперь уже будет двухсторонняя связь. То есть мы, например, можем совершить выборку из базы с помощью метода get объекта AuthorInfo, и при этом автоматически будет получен из базы связанный с ним объект Author.
Для двусторонней связности объектов связанных строк, необходимо изменить класс AuthorInfo.
package HibernateApps;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.OneToOne;
import javax.persistence.Table;
// Это класс с таблицы author_info.
@Entity
@Table(name = “author_info”)
public class AuthorInfo {
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY
)
@Column(name = “id”)
private int id;
@Column(name = “books_written_number”)
private int booksWritten;
@Column(name = “country”)
private String country;
// Для двусторонней связанности указываем
// в mappedBy каким полем класс Author
// связан с данным классом AuthorInfo.
@OneToOne(mappedBy = “authorInfo”,
cascade = CascadeType.ALL)
private Author author;
public AuthorInfo() {
}
public AuthorInfo(int booksWritten,
String country) {
this.booksWritten = booksWritten;
this.country = country;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getBooksWritten() {
return booksWritten;
}
public void setBooksWritten(
int booksWritten) {
this.booksWritten = booksWritten;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
// В прошлом уроке мы передавали
// объект AuthorInfo в сеттер
// объекта Author для того чтобы эти
// объекты были связаны как связаны
// строки этих объектов в БД.
// Чтобы можно было так связать
// эти объекты наоборот, то есть
// в сеттер объекта Author
// передать объект Author,
// нужно еще и передать
// текущий(this) объект AuthorInfo
// объекту Author потому что если
// этого не сделать то объект Author
// не будет знать ничего
// о объекте AuthorInfo.
// Уточним что класс, в котором
// mappedBy не связан с тем, в котором
// JoinColumn напрямую. Связанным напрямую
// с другим классом можно назвать класс,
// в котором JoinColumn. Поэтому обратную
// связанность мы скорее имитируем
// использованием mappedBy
// и конструкциями типа
// author.setAuthorInfo(this);
author.setAuthorInfo(this);
}
@Override
public String toString() {
return “AuthorInfo [id=” + id + “, ”
+ “booksWritten=”
+ booksWritten + “, country=”
+ country + “, author=”
+ author + “]”;
}
}
Теперь давайте создадим два объекта, свяжем их теперь уже используя сеттер объекта AuthorInfo и добавим их в БД.
package HibernateApps;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateApp {
public static void main(String[] args) {
// create session factory
SessionFactory factory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Author.class)
.addAnnotatedClass(AuthorInfo.class)
.addAnnotatedClass(AuthorBooks.class)
.buildSessionFactory();
// create session
Session session = factory.getCurrentSession();
try {
session.beginTransaction();
//создадим два объекта и свяжем их
//только через сеттер объекта
//authorInfo, а не author как
//в прошлых уроках
AuthorInfo authorInfo = new AuthorInfo(10, “Great Britain”);
Author author = new Author(“JJ Rowling”);
//связываем
authorInfo.setAuthor(author);
//при сохранении authorInfo сохраниться
//в базу и author поскольку они связаны
session.save(authorInfo);
AuthorInfo authorInfo1 = session.get(AuthorInfo.class, 1);
System.out.println(authorInfo1);
//строкой ниже удаляться связанные строки
//таблиц через связанные объекты этих строк.
session.delete(authorInfo1);
AuthorInfo authorInfo2 = session.get(AuthorInfo.class, 1);
System.out.println(authorInfo2);
session.getTransaction().commit();
}
catch (Exception e){
session.getTransaction().rollback();
e.printStackTrace();
}
finally {
session.close();
}
}
}
Давайте запустим нашу программу.
Как видим, произошло два запроса на вставку, хотя метод save мы вызывали только над одним объектом actorinfo. Это доказывает, что каскадная PERSIST операция сработала и произошел insertне только объекта authorinfo в таблицу author_info, а и связанного с ним объекта author в таблицу author. То есть двусторонняя связь работает.
Далее с помощью get мы выбрали из таблицы author_info только что положенную туда строку и поместили ее в объект authorinfo1, в консоли можно увидеть содержимое объекта authorinfo1, и как видим он, содержит объект author, то есть опять таки, для выборки связь тоже работает.
После удаления из базы связанных объектов на консоль вывелось null, значит объекты успешно удалились из базы.
Проверим на всякий случай и базу удалились ли обе строки в таблицах:
В базе данных обычно существуют связанные таблицы.
Все стандартные виды связей (1-к-1, 1-ко-Многим, Многие-Ко-Многим) можно реализовать и между java классами тех таблиц, которые связаны между собой в базе.
Объекты этих связанных классов, очевидно, тоже могут быть связанными. Связанные объекты связанных классов, это связанные строки связанных таблиц.
Благодаря этому, можно сделать так, чтобы при добавлении в таблицу одной строки через объект, одновременно добавлялась в другую таблицу другая строка, благодаря тому, что объекты этих двух строк связаны в программе. То есть в БД добавятся связанные строки.
Также можно сделать так, чтобы при удалении одной строки из таблицы через объект, одновременно удалялась и связанная с этой строкой строка в другой таблице, опять же благодаря тому что объекты этих двух строк связаны в программе.
Также помимо одновременного удаления и добавления есть и другие каскадные операции, которые будут рассмотрены в конце урока.
Давайте с помощью SQL запросов создадим две связанные таблицы – таблицу author и таблицу author_info. В одной таблице хранятся авторы, в другой информация о них.
Между этими таблицами будет тип связи 1-к-1, то есть одной строке таблицы author, соответствует одна строка в таблице author_info. То есть вся информация о конкретном авторе храниться в одной строке другой таблицы.
Выполним эти SQL запросы:
Теперь создадим класс только что созданной таблицы author_info. В нем пока ничего нового.
package HibernateApps;
import javax.persistence.CascadeType;
@Entity
//это класс с таблицы author_info
@Table(name = “author_info”)
public class AuthorInfo {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY)
@Column(name=”id”)
private int id;
@Column(name=”books_written_number”)
private int booksWritten;
@Column(name=”country”)
private String country;
public AuthorInfo() {}
public AuthorInfo(int booksWritten,
String country) {
this.booksWritten = booksWritten;
this.country = country;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getBooksWritten() {
return booksWritten;
}
public void setBooksWritten(
int booksWritten) {
this.booksWritten = booksWritten;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return “AuthorInfo [id=” + id +
“, booksWritten=”
+ booksWritten +
“, country=”
+ country + “]”;
}
}
Теперь создадим класс только что созданной таблицы author. В нем мы связываем классы связью 1-к-1.
package HibernateApps;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.Table;
import javax.persistence.OneToOne;
import javax.persistence.JoinColumn;
import javax.persistence.CascadeType;
//это класс с таблицы author
@Entity
@Table(name = “author”)
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = “id”)
private int id;
@Column(name = “name”)
private String authorName;
//Помним что в БД у нас настроена связь 1-к-1
//между таблицами author и author_info.
//Поэтому мы можем настроить связь мужду
//классами этих таблиц – Author и AuthorInfo.
//Точнее говоря настраивается
//связь между объектом текущего класса
//Author и объектом AuthorInfo класса
//AuthorInfo, который можно увидеть ниже.
//Тоесть связь настраивается между двумя
//объектами связанных строк связных таблиц.
//Аннотацией @OneToOne настраивается
//этот вид связи
//Кратко о том что такое cascade
//В конце урока. Пока оставим CascadeType.ALL
@OneToOne(cascade = CascadeType.ALL)
//Указываем каким атрибутом таблица данного
//класса связана с таблицей связанного.
@JoinColumn(name = “author_info_id”)
//Ниже объект связанной таблицы
private AuthorInfo authorInfo;
public Author() {
}
public Author(String authorName) {
this.authorName = authorName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
public void setAuthorInfo(AuthorInfo authorInfo) {
this.authorInfo = authorInfo;
}
@Override
public String toString() {
return “Author [id=” + id + “, ”
+ “authorName=” + authorName + “, ”
+ “authorInfo=”
+ authorInfo + “]”;
}
}
Работа со связанными классами. Каскадная PERSIST операция.
Теперь давайте создадим два объекта, свяжем их и добавим их в БД.
package HibernateApps;
import java.util.List;
public class HibernateApp {
public static void main(String[] args) {
//Делаем так чтобы sessionFactory анализировал
//классы Author и AuthorInfo.
SessionFactory sessionFactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Author.class)
.addAnnotatedClass(AuthorInfo.class)
.buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
try {
//Ниже создаются два объекта созданных классов.
Author author = new Author(“JJ Rouling”);
AuthorInfo authorInfo = new AuthorInfo(
1, “Great Britain”);
//Через сеттер связываем два созданных объекта
author.setAuthorInfo(authorInfo);
session.beginTransaction();
//Ниже при добавлении в таблицу author одной
//строки через объект author, добавляется
//в таблицу author_info другая строка,
//благодаря тому что объекты этих двух
//строк мы только что связали.
//Это называется каскадная PERSIST операция,
//тоесть когда сохраняется один объект в базу
//сохраняется и связанный с ним.
session.save(author);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
Давайте запустим нашу программу.
Как видим, произошло два запроса на вставку, хотя метод save мы вызывали только над одним объектом actor. Это доказывает что каскадная PERSIST операция сработала и произошел insert не только объекта author в таблицу author, а и связанного с ним объекта authorinfo в таблицу author_info.
Как видим в обе таблицы успешно добавились объекты.
Работа со связанными классами. Каскадная REMOVE операция.
Теперь продемонстрируем каскадную REMOVE операцию.
package HibernateApps;
import java.util.List;
public class HibernateApp {
public static void main(String[] args) {
//Делаем так чтобы sessionFactory анализировал
//классы Author и AuthorInfo.
SessionFactory sessionFactory = new Configuration()
.configure(“hibernate.cfg.xml”)
.addAnnotatedClass(Author.class)
.addAnnotatedClass(AuthorInfo.class)
.buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
try {
session.beginTransaction();
//Давайте извлечем из таблицы actor строку
//которую мы только что туда добавили.
Author author = session.get(Author.class, 1);
//Вместе с извлеченной строкой из таблицы author
//извлекается и связанная с ним строка из
//таблицы author_info. Тоесть в объекте author
//строки из таблицы author
//уже будет присутствовать объект связанной
//с этой строкой строки из таблицы author_info.
System.out.println(author);
//Ниже при удалении одной строки
//из таблицы author через объект author,
//одновременно удаляется и связанная с этой
//строкой строка из таблицы author_info,
//опять же благодаря тому,
//что объекты этих двух строк связаны в программе.
//Это называется каскадная REMOVE операция,
//тоесть когда удаляется один объект в базе
//удаляется и связанный с ним из базы.
session.delete(author);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
e.printStackTrace();
} finally {
session.close();
}
}
}
Давайте запустим нашу программу.
Как видим, в объекте author присутствует объект authorInfo, который соответствует связанной строке из таблицы author_info.
Далее можно увидеть, что произошло два запроса на удаление, хотя метод delete мы вызывали только над одним объектом actor. Это доказывает, что каскадная REMOVE операция сработала и произошел delete не только объекта author в таблице author, а и связанного с ним объекта в таблице author_info.
Связанные строки успешно удалились из обеих таблиц.
О каскадных операциях
Только что мы рассмотрели две каскадные операции PERSIST(добавление связанных объектов в БД), REMOVE(удаление связанных объектов из БД).
Но есть и другие – MERGE, DETACH и REFRESH.
Детали их работы и настройки покажем на примере.
package HibernateApps;
import java.util.List;
@Entity
//это класс с таблицы author
@Table(name = “author”)
public class Author {
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY
)
@Column(name=”id”)
private int id;
@Column(name=”name”)
private String authorName;
//CascadeType.ALL – все каскадные операции разрешены.
//Подробное пояснение типов cascade приведено ниже
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn(name=”author_info_id”)
private AuthorInfo authorInfo;
public Author() {
}
public Author(String authorName) {
this.authorName = authorName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
public void setAuthorInfo(AuthorInfo authorInfo) {
this.authorInfo = authorInfo;
}
@Override
public String toString() {
return “Author [id=” + id + “, ”
+ “authorName=” + authorName
+ “, authorInfo=”
+ authorInfo + “]”;
}
}
CascadeType.ALL значит, что все каскадные операции разрешены. Вместо CascadeType.ALL можно было написать так: cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} и это было бы то же самое что CascadeType.ALL.
Любой из каскадирований можно убирать или добавлять в этих скобочках, то есть если убрать CascadeType.REMOVE, то каскадная операция REMOVEработать больше не будет со связанными объектами Author и AuthorInfo. При удалении объекта Author из БД, связанный с ним объект AuthorInfo из БД удаляться не будет.
Кратко рассмотрим каждую каскадную операцию на примере связи 1-к-1 объектов Author и AuthorInfo.
DETACH – если объект класса Author внезапно перестает принадлежать сессии, тогда и связанный с ним объект класса AuthorInfo тоже перестает ей принадлежать. Это можно сделать с помощью функции session.detach().
MERGE – если через объект класса Author происходит обновление строки в таблице author, то тогда через объект класса AuthorInfo, который связан с этим объектом Author, происходит и обновление связанной с этой строкой строки в таблице author_info. Это можно сделать с помощью функции session.save().
PERSIST – если через объект класса Author происходит сохранение новой строки в таблице author, то тогда через объект класса AuthorInfo, который связан с этим объектом Author, тоже происходит сохранение новой строки в таблицу author_info. Это можно сделать с помощью функции session.save().
REMOVE – если через объект класса Author происходит удаление строки в таблице author, то тогда через объект класса AuthorInfo, который связан с этим объектом Author, происходит и удаление связанной с этой строкой строки в таблице author_info. Это можно сделать с помощью функции session.delete().
REFRESH – если в программе происходит обновление объекта класса Author (имеется в виду перезвлечение строки таблицы в объект. Тоесть, происходит обновление объекта в программе, не в БД), то происходит и обновление (перевзвлечение) объекта, который связан с объектом класса AuthorInfo. Это можно сделать с помощью функции session.refresh().
Важно помнить, что по умолчанию cascade не задан, то есть он ничего не содержит. Поэтому нужно явно устанавливать нужный тип cascade между таблицами.