Файл: Практикум для студентов, обучающихся по направлению подготовки 09. 03. 04 Программная инженерия.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 176
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
14.2. Задание
Создать отдельный репозиторий Git. Создать простой html-документ, который будет содержать вашу фамилию, имя, номер группы, номер варианта.
Создать контроллер, который будет возвращать данный статический документ при переходе на url «/home». Выполнить задание в зависимости с вариантом индивидуального задания.
14.3. Варианты индивидуального задания
1) Создать класс Student с полями firstName, lastName, middleName.
Создать класс Group с полем groupName. Создать классы-контроллеры для
9) Создать класс Phone с полями name, creationYear. Создать класс
Manufacture с полями name, address. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
10) Создать класс Student с полями firstName, lastName, middleName.
Создать класс University с полями name, creationDate. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
11) Создать класс Address с полями addressText, zipCode. Создать класс
Building с полями creationDate, type. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
12) Создать класс Card с полями cardNumber, code. Создать класс Bank с полями name, address. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
13) Создать класс Product с полями name, price. Создать класс Market с полями name, address. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
14) Создать класс Patient с полями firstName, lastName. Создать класс
Patient с полями firstName, lastName, position. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа.
Сами объекты хранить в памяти.
15) Создать класс Footballer с полями firstName, lastName. Создать класс
Team с полями name, creationDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
15. ПРАКТИЧЕСКАЯ РАБОТА № 15
Цель работы
Использование Hibernate в Spring framework.
15.1. Теоретическая часть
Зачастую для очень сложной системы требуется какой-то удобный инструмент для взаимодействия с базой данных. Нужно представить таблицы в виде чего-то удобного, с чем легко взаимодействовать. И это, конечно же, объекты. Если таблицу представить в виде класса, а строки в виде некоторых объектов, возникает очень удобная абстракция. И данная абстракция называется Object Relational Mapping (ORM). Существует огромное количество различных ORM, но наиболее известной является Hibernate.
Функции, предоставляемые ORM:
– удобное преобразование данных таблицы в объекты, что существенно упрощает взаимодействие с базой данных (БД);
– управление своими объектами, отслеживание изменений в БД и обновление их. Также при обновлении объектов непосредственно внесение изменений в БД;
– возможность производить lazy loading зависящих объектов;
– возможность переводить JOIN в объект (в некоторых случаях).
Разберем работу с Hibernate сразу на примерах. В качестве базы данных будет использоваться PostgreSQL, но вы можете использовать любую реляционную БД.
Пример использования Hibernate
Допустим, у нас есть таблица user: create table users ( id int, first_name varchar(100), last_name varchar(100)
);
Создадим класс, который будет представлять пользователя:
@Entity
@Table(name = "users")
@Getter
@Setter public class User {
@Id private Long id;
@Column(name = "first_name") private String firstName;
@Column(name = "last_name") private String lastName;
}
Теперь нам остается настроить конфигурацию (пока что не будем использовать автоконфигурацию boot, чтобы понять, что вообще требуется
Hibernate для старта).
Не забудьте добавить зависимость драйвера вашей базы данных, а также добавьте (если нет) HikariCP. Добавьте данные бины в конфигурации:
@Bean public HikariDataSource dataSource() {
HikariConfig config = new HikariConfig(); config.setJdbcUrl("your_url"); config.setUsername("your_username"); config.setPassword("your_password"); return new HikariDataSource(config);
}
@Bean public LocalSessionFactoryBean factoryBean(DataSource dataSource) {
LocalSessionFactoryBean sessionFactoryBean = new
LocalSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource);
}
Готово – теперь при сохранении пользователя при отсутствии у него id он будет автоматически генерироваться. Но, скорее всего, если вы попытаетесь сохранить пользователя, у вас это не получится. Проблема в транзакциях – Hibernate требует для всего текущей транзакции, поэтому добавьте в свой код создание транзакции и коммит: var transaction = session.beginTransaction(); session.saveOrUpdate(user); transaction.commit();
О транзакциях подробнее мы поговорим в следующих заданиях.
У объекта, подконтрольного Hibernate, существует свой жизненный цикл (рисунок 34).
Рисунок 34 – Жизненный цикл объекта, подконтрольного Hibernate
Мы не будем особо углубляться в особенности жизненного цикла, главное, что нужно запомнить, – это в состоянии Persistent все изменения на объекте будут сохраняться в БД, а также происходит dirty checking – проверка, соответствует ли объект строке в БД, не происходили ли изменения на БД. Но даже учитывая, что фактически Hibernate сам видит, что мы что-то поменяли,
16. ПРАКТИЧЕСКАЯ РАБОТА № 16
Цель работы
Изучение видов связей между сущностями в Hibernate. Использование транзакций.
16.1. Теоретическая часть
Hibernate также предоставляет множество удобного функционала для работы со связями между таблицами. Немного погрузимся в данную тему на практическом примере. Допустим, у нас есть таблица пользователей «users»
(возьмем из предыдущего задания) и собаки («dogs»), которые принадлежат пользователям: create table dogs ( id int, user_id int, name varchar(100), breed varchar(100)
); create sequence dogs_sequence start 1 increment 1;
Создадим наш класс собаки, и сразу сделаем так, чтобы мы могли подтянуть пользователя, чья это собака.
@Table(name = "dogs")
@Entity
@Getter
@Setter public class Dog {
@Id private Long id; private String name; private String breed;
@ManyToOne public User user;
}
Все, теперь добавим в таблицу парочку собачек (не забудьте установить в значение user_id существующего пользователя из таблицы users), и попробуем их получить и вызвать метод getUser(). Например, вот так:
@Service
@RequiredArgsConstructor public class DogService { private final SessionFactory sessionFactory; private Session session;
@PostConstruct public void init() { session = sessionFactory.openSession();
} public User getUserByDog(Long dogId) { return session.createQuery("from Dog where id =
:id", Dog.class)
.setParameter("id",dogId).getSingleResult().getUser();
}
}
И метод в контроллере:
@GetMapping(value = "/dog/{dogId}/user") public @ResponseBody User getDogUser(@PathVariable("dogId")
Long dogId){ return dogService.getUserByDog(dogId);
}
А теперь хотелось бы, чтобы и у пользователя можно было бы сразу получить всех собачек. Подтягиваются они отдельным запросом при вызове непосредственно геттера. Такое поведение «ленивого» подтягивания данных называется lazy loading:
@Entity
@Table(name = "users")
@Getter
@Setter public class User {
@Id
@SequenceGenerator(name = "users_seq", sequenceName =
"users_sequence", allocationSize = 1)
@GeneratedValue(generator = "users_seq", strategy =
GenerationType.SEQUENCE) private Long id;
@Column(name = "first_name") private String firstName;
@Column(name = "last_name") private String lastName;
@OneToMany(mappedBy = "user") private List dogs;
}
Вызываем метод для получения всех пользователей или пользователя и видим подобный результат:
Что произошло? У нас всего лишь по одной записи в каждой таблице!
Дело в том, что при попытке сериализовать вызывается метод в User на получение всех собачек, а там вызывается опять метод для получения пользователя, чтоб сериализовать полностью объект. И получается такая страшная рекурсия. От этого избавиться может помочь аннотация, чтобы спрятать от сериализации один из методов. Например, нам не так критично, чтобы у собаки при сериализации был виден пользователь, добавим над полем user аннотацию JsonIgnore, и попробуем снова запустить:
Вот, теперь все правильно и красиво. Но если все же нам нужно для собаки выводить пользователя? Тогда стоит воспользоваться паттерном Data
Transfer Object (DTO) – мы используем отдельный простой класс, в который превращаем наш класс сущности, и возвращаем уже класс DTO.
16.2. Задание
Создать связь Один-ко-многим между сущностями из предыдущего задания и проверить работу lazy loading.
17. ПРАКТИЧЕСКАЯ РАБОТА № 17
Цель работы
Знакомство с Criteria API в Hibernate.
17.1. Теоретическая часть
HQL – достаточно удобен для написания запросов, и покрывает практически все потребности при разработке. Но есть одно, с чем HQL не справится – динамическое создание запросов. Допустим, нам нужно в зависимости от запроса пользователя по-разному фильтровать данные для ответа. В этом поможет Criteria API.
Для полного создания запроса нам потребуется 3 объекта:
1) CriteriaBuilder – это, соответственно, сам билдер запроса;
2) CriteriaQuery – запрос;
3) Root – это основная сущность, для которой делается запрос.
Создадим билдер запроса:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery dogCriteriaQuery = builder.createQuery(Dog.class);
Root root = dogCriteriaQuery.from(Dog.class);
Теперь создадим сам запрос – простую сортировку по породе.
Выглядеть это будет так: dogCriteriaQuery.select(root).orderBy(builder.asc(root.get(
"breed")));
А теперь остается получить значения и вернуть как результат метода:
Query query = session.createQuery(dogCriteriaQuery); return query.getResultList();
Criteria API достаточно громоздкий, но при этом очень подвижный и функциональный способ выполнения запросов. С помощью него можно создавать запросы любой сложности, с JOIN, сортировками, фильтрацией.
Поэтому если потребуется динамический построитель запросов, Criteria API – неплохой выбор. Подробнее про Criteria API можно почитать здесь:
18. ПРАКТИЧЕСКАЯ РАБОТА № 18
Цель работы
Знакомство с репозиториями и сервисами, реализация в проекте.
Взаимодействие с Spring Data JPA.
18.1. Теоретическая часть
Самой простой, но при этом достаточно эффективной архитектурой является слоистая архитектура, при которой приложение делится на отдельные независимые слои. Чаще всего используется 3 слоя:
1) слой представлений – в Spring реализуется с использованием контроллеров;
2) слой бизнес-логики (сервисов) – реализуется с помощью сервисов;
3) слой данных – реализуется с помощью репозиториев.
Сервисы и репозитории
Сервис – специальный слой, в котором хранится вся бизнес-логика. В данном слое должно быть как можно меньше взаимодействия со сторонними библиотеками для большей поддерживаемости. Для данных классов лучше использовать аннотацию Service. Она ничем не отличается от Component, просто для лучшей выразительности лучше пользоваться ей.
Также наиболее верно использовать интерфейсы на стыке между слоями. Зачем? Это паттерн – инверсия зависимости, для того, чтобы верхние слои не зависели от нижних, и можно было с легкостью подменить одну имплементацию на другую. Поэтому наиболее правильный способ создания репозитория – определение интерфейса, а затем его имплементации с аннотацией Service. Пример: public interface UserService {
List getUsers(); void saveOrUpdate(User user);
}
@Service public class UserServiceImpl implements UserService {
Создать отдельный репозиторий Git. Создать простой html-документ, который будет содержать вашу фамилию, имя, номер группы, номер варианта.
Создать контроллер, который будет возвращать данный статический документ при переходе на url «/home». Выполнить задание в зависимости с вариантом индивидуального задания.
14.3. Варианты индивидуального задания
1) Создать класс Student с полями firstName, lastName, middleName.
Создать класс Group с полем groupName. Создать классы-контроллеры для
создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
2) Создать класс Worker с полями firstName, lastName, middleName.
Создать класс Manufacture c полями name, address. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
3) Создать класс Book с полями name, creationDate. Создать класс Author с полями firstName, lastName, middleName, birthDate. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
4) Создать класс Departure с полями type,departureDate. Создать класс
PostOffice с полями name, cityName. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
5) Создать класс Game с полями name, creationDate. Создать класс
GameAuthor с полями nickname, birthDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
6) Создать класс Post с полями text, creationDate. Создать класс User с полями firstName, lastName, middleName, birthDate. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
7) Создать класс Item с полями name, creationDate, price. Создать класс
Order с полями orderDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
8) Создать класс Level с полями complexity, levelName. Создать класс
Game с полями name, creationDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
2) Создать класс Worker с полями firstName, lastName, middleName.
Создать класс Manufacture c полями name, address. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
3) Создать класс Book с полями name, creationDate. Создать класс Author с полями firstName, lastName, middleName, birthDate. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
4) Создать класс Departure с полями type,departureDate. Создать класс
PostOffice с полями name, cityName. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
5) Создать класс Game с полями name, creationDate. Создать класс
GameAuthor с полями nickname, birthDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
6) Создать класс Post с полями text, creationDate. Создать класс User с полями firstName, lastName, middleName, birthDate. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
7) Создать класс Item с полями name, creationDate, price. Создать класс
Order с полями orderDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
8) Создать класс Level с полями complexity, levelName. Создать класс
Game с полями name, creationDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
9) Создать класс Phone с полями name, creationYear. Создать класс
Manufacture с полями name, address. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
10) Создать класс Student с полями firstName, lastName, middleName.
Создать класс University с полями name, creationDate. Создать классы- контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
11) Создать класс Address с полями addressText, zipCode. Создать класс
Building с полями creationDate, type. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
12) Создать класс Card с полями cardNumber, code. Создать класс Bank с полями name, address. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
13) Создать класс Product с полями name, price. Создать класс Market с полями name, address. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
14) Создать класс Patient с полями firstName, lastName. Создать класс
Patient с полями firstName, lastName, position. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа.
Сами объекты хранить в памяти.
15) Создать класс Footballer с полями firstName, lastName. Создать класс
Team с полями name, creationDate. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти.
15. ПРАКТИЧЕСКАЯ РАБОТА № 15
Цель работы
Использование Hibernate в Spring framework.
15.1. Теоретическая часть
Зачастую для очень сложной системы требуется какой-то удобный инструмент для взаимодействия с базой данных. Нужно представить таблицы в виде чего-то удобного, с чем легко взаимодействовать. И это, конечно же, объекты. Если таблицу представить в виде класса, а строки в виде некоторых объектов, возникает очень удобная абстракция. И данная абстракция называется Object Relational Mapping (ORM). Существует огромное количество различных ORM, но наиболее известной является Hibernate.
Функции, предоставляемые ORM:
– удобное преобразование данных таблицы в объекты, что существенно упрощает взаимодействие с базой данных (БД);
– управление своими объектами, отслеживание изменений в БД и обновление их. Также при обновлении объектов непосредственно внесение изменений в БД;
– возможность производить lazy loading зависящих объектов;
– возможность переводить JOIN в объект (в некоторых случаях).
Разберем работу с Hibernate сразу на примерах. В качестве базы данных будет использоваться PostgreSQL, но вы можете использовать любую реляционную БД.
Пример использования Hibernate
Допустим, у нас есть таблица user: create table users ( id int, first_name varchar(100), last_name varchar(100)
);
Создадим класс, который будет представлять пользователя:
@Entity
@Table(name = "users")
@Getter
@Setter public class User {
@Id private Long id;
@Column(name = "first_name") private String firstName;
@Column(name = "last_name") private String lastName;
}
Теперь нам остается настроить конфигурацию (пока что не будем использовать автоконфигурацию boot, чтобы понять, что вообще требуется
Hibernate для старта).
Не забудьте добавить зависимость драйвера вашей базы данных, а также добавьте (если нет) HikariCP. Добавьте данные бины в конфигурации:
@Bean public HikariDataSource dataSource() {
HikariConfig config = new HikariConfig(); config.setJdbcUrl("your_url"); config.setUsername("your_username"); config.setPassword("your_password"); return new HikariDataSource(config);
}
@Bean public LocalSessionFactoryBean factoryBean(DataSource dataSource) {
LocalSessionFactoryBean sessionFactoryBean = new
LocalSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setPackagesToScan("your_packages_to_scan");
Properties properties = new Properties(); properties.setProperty("hibernate.dialect",
"your_dialect"); sessionFactoryBean.setHibernateProperties(properties); return sessionFactoryBean;
}
@Bean public PlatformTransactionManager platformTransactionManager(LocalSessionFactoryBean factoryBean){
HibernateTransactionManager transactionManager = new
HibernateTransactionManager(); transactionManager.setSessionFactory(factoryBean.getOb ject()); return transactionManager;
}
А теперь попробуем воспользоваться тем, что сделали. Создадим класс, добавим SessionFacrory, создадим объект класса Session – он является основным интерфейсом по взаимодействию с БД – нечто, похожее на connection, только адаптер к нему. Для создания запроса воспользуемся языком HQL – специальный язык запросов от Hibernate, очень похожий на
SQL.
@Component
@RequiredArgsConstructor public class UserService { private final SessionFactory sessionFactory; private Session session;
@PostConstruct void init() { session = sessionFactory.openSession();
} public List getUsers() {
Properties properties = new Properties(); properties.setProperty("hibernate.dialect",
"your_dialect"); sessionFactoryBean.setHibernateProperties(properties); return sessionFactoryBean;
}
@Bean public PlatformTransactionManager platformTransactionManager(LocalSessionFactoryBean factoryBean){
HibernateTransactionManager transactionManager = new
HibernateTransactionManager(); transactionManager.setSessionFactory(factoryBean.getOb ject()); return transactionManager;
}
А теперь попробуем воспользоваться тем, что сделали. Создадим класс, добавим SessionFacrory, создадим объект класса Session – он является основным интерфейсом по взаимодействию с БД – нечто, похожее на connection, только адаптер к нему. Для создания запроса воспользуемся языком HQL – специальный язык запросов от Hibernate, очень похожий на
SQL.
@Component
@RequiredArgsConstructor public class UserService { private final SessionFactory sessionFactory; private Session session;
@PostConstruct void init() { session = sessionFactory.openSession();
} public List
return session.createQuery("select u from User u",
User.class).getResultList();
}
}
Затем создадим контроллер и попробуем вызвать метод. Он должен вернуть нам пустой массив. Теперь добавим в таблицу пару строк, и опять вызовем данный эндпоинт. Теперь наш массив заполнен значениями.
Для того, чтобы мы могли сохранять программно какие-то объекты или сохранять изменения уже существующих строк, используется метод saveOrUpdate.
User user = new User(); user.setFirstName("Vasya"); user.setLastName("Dima"); session.saveOrUpdate(user);
Только не забудьте добавить генерацию первичного ключа.
Воспользуемся способом генерации первичного ключа при помощи sequence.
Создаем sequence: create sequence users_sequence start 1 increment 1;
Добавляем нужные аннотации:
@Entity
@Table(name = "users")
@Getter
@Setter public class User {
@Id
@SequenceGenerator(name = "users_seq", sequenceName =
"users_sequence", allocationSize = 1)
@GeneratedValue(generator = "users_seq", strategy =
GenerationType.SEQUENCE) private Long id;
@Column(name = "first_name") private String firstName;
@Column(name = "last_name") private String lastName;
User.class).getResultList();
}
}
Затем создадим контроллер и попробуем вызвать метод. Он должен вернуть нам пустой массив. Теперь добавим в таблицу пару строк, и опять вызовем данный эндпоинт. Теперь наш массив заполнен значениями.
Для того, чтобы мы могли сохранять программно какие-то объекты или сохранять изменения уже существующих строк, используется метод saveOrUpdate.
User user = new User(); user.setFirstName("Vasya"); user.setLastName("Dima"); session.saveOrUpdate(user);
Только не забудьте добавить генерацию первичного ключа.
Воспользуемся способом генерации первичного ключа при помощи sequence.
Создаем sequence: create sequence users_sequence start 1 increment 1;
Добавляем нужные аннотации:
@Entity
@Table(name = "users")
@Getter
@Setter public class User {
@Id
@SequenceGenerator(name = "users_seq", sequenceName =
"users_sequence", allocationSize = 1)
@GeneratedValue(generator = "users_seq", strategy =
GenerationType.SEQUENCE) private Long id;
@Column(name = "first_name") private String firstName;
@Column(name = "last_name") private String lastName;
}
Готово – теперь при сохранении пользователя при отсутствии у него id он будет автоматически генерироваться. Но, скорее всего, если вы попытаетесь сохранить пользователя, у вас это не получится. Проблема в транзакциях – Hibernate требует для всего текущей транзакции, поэтому добавьте в свой код создание транзакции и коммит: var transaction = session.beginTransaction(); session.saveOrUpdate(user); transaction.commit();
О транзакциях подробнее мы поговорим в следующих заданиях.
У объекта, подконтрольного Hibernate, существует свой жизненный цикл (рисунок 34).
Рисунок 34 – Жизненный цикл объекта, подконтрольного Hibernate
Мы не будем особо углубляться в особенности жизненного цикла, главное, что нужно запомнить, – это в состоянии Persistent все изменения на объекте будут сохраняться в БД, а также происходит dirty checking – проверка, соответствует ли объект строке в БД, не происходили ли изменения на БД. Но даже учитывая, что фактически Hibernate сам видит, что мы что-то поменяли,
и сохраняет изменения, рекомендуется использовать метод saveOrUpdate на
Persistent объекты для читаемости и понятности кода.
Для лучшего изучения данной темы рекомендуется ознакомиться с официальной документацией
Hibernate
(https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/).
15.2. Задание
Изменить программу с предыдущего задания так, чтобы объекты хранились в базе данных PostgreSQL вместо памяти компьютера.
Persistent объекты для читаемости и понятности кода.
Для лучшего изучения данной темы рекомендуется ознакомиться с официальной документацией
Hibernate
(https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/).
15.2. Задание
Изменить программу с предыдущего задания так, чтобы объекты хранились в базе данных PostgreSQL вместо памяти компьютера.
16. ПРАКТИЧЕСКАЯ РАБОТА № 16
Цель работы
Изучение видов связей между сущностями в Hibernate. Использование транзакций.
16.1. Теоретическая часть
Hibernate также предоставляет множество удобного функционала для работы со связями между таблицами. Немного погрузимся в данную тему на практическом примере. Допустим, у нас есть таблица пользователей «users»
(возьмем из предыдущего задания) и собаки («dogs»), которые принадлежат пользователям: create table dogs ( id int, user_id int, name varchar(100), breed varchar(100)
); create sequence dogs_sequence start 1 increment 1;
Создадим наш класс собаки, и сразу сделаем так, чтобы мы могли подтянуть пользователя, чья это собака.
@Table(name = "dogs")
@Entity
@Getter
@Setter public class Dog {
@Id private Long id; private String name; private String breed;
@ManyToOne public User user;
}
Все, теперь добавим в таблицу парочку собачек (не забудьте установить в значение user_id существующего пользователя из таблицы users), и попробуем их получить и вызвать метод getUser(). Например, вот так:
@Service
@RequiredArgsConstructor public class DogService { private final SessionFactory sessionFactory; private Session session;
@PostConstruct public void init() { session = sessionFactory.openSession();
} public User getUserByDog(Long dogId) { return session.createQuery("from Dog where id =
:id", Dog.class)
.setParameter("id",dogId).getSingleResult().getUser();
}
}
И метод в контроллере:
@GetMapping(value = "/dog/{dogId}/user") public @ResponseBody User getDogUser(@PathVariable("dogId")
Long dogId){ return dogService.getUserByDog(dogId);
}
А теперь хотелось бы, чтобы и у пользователя можно было бы сразу получить всех собачек. Подтягиваются они отдельным запросом при вызове непосредственно геттера. Такое поведение «ленивого» подтягивания данных называется lazy loading:
@Entity
@Table(name = "users")
@Getter
@Setter public class User {
@Id
@SequenceGenerator(name = "users_seq", sequenceName =
"users_sequence", allocationSize = 1)
@GeneratedValue(generator = "users_seq", strategy =
GenerationType.SEQUENCE) private Long id;
@Column(name = "first_name") private String firstName;
@Column(name = "last_name") private String lastName;
@OneToMany(mappedBy = "user") private List
}
Вызываем метод для получения всех пользователей или пользователя и видим подобный результат:
Что произошло? У нас всего лишь по одной записи в каждой таблице!
Дело в том, что при попытке сериализовать вызывается метод в User на получение всех собачек, а там вызывается опять метод для получения пользователя, чтоб сериализовать полностью объект. И получается такая страшная рекурсия. От этого избавиться может помочь аннотация, чтобы спрятать от сериализации один из методов. Например, нам не так критично, чтобы у собаки при сериализации был виден пользователь, добавим над полем user аннотацию JsonIgnore, и попробуем снова запустить:
Вот, теперь все правильно и красиво. Но если все же нам нужно для собаки выводить пользователя? Тогда стоит воспользоваться паттерном Data
Transfer Object (DTO) – мы используем отдельный простой класс, в который превращаем наш класс сущности, и возвращаем уже класс DTO.
16.2. Задание
Создать связь Один-ко-многим между сущностями из предыдущего задания и проверить работу lazy loading.
17. ПРАКТИЧЕСКАЯ РАБОТА № 17
Цель работы
Знакомство с Criteria API в Hibernate.
17.1. Теоретическая часть
HQL – достаточно удобен для написания запросов, и покрывает практически все потребности при разработке. Но есть одно, с чем HQL не справится – динамическое создание запросов. Допустим, нам нужно в зависимости от запроса пользователя по-разному фильтровать данные для ответа. В этом поможет Criteria API.
Для полного создания запроса нам потребуется 3 объекта:
1) CriteriaBuilder – это, соответственно, сам билдер запроса;
2) CriteriaQuery – запрос;
3) Root – это основная сущность, для которой делается запрос.
Создадим билдер запроса:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery
Root
Теперь создадим сам запрос – простую сортировку по породе.
Выглядеть это будет так: dogCriteriaQuery.select(root).orderBy(builder.asc(root.get(
"breed")));
А теперь остается получить значения и вернуть как результат метода:
Query
Criteria API достаточно громоздкий, но при этом очень подвижный и функциональный способ выполнения запросов. С помощью него можно создавать запросы любой сложности, с JOIN, сортировками, фильтрацией.
Поэтому если потребуется динамический построитель запросов, Criteria API – неплохой выбор. Подробнее про Criteria API можно почитать здесь:
https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/querycriteria.
html.
17.2. Задание
Добавить возможность фильтрации по всем полям всех классов с использованием Criteria API в Hibernate для программы из предыдущего задания. Добавить эндпоинты для каждой фильтрации.
html.
17.2. Задание
Добавить возможность фильтрации по всем полям всех классов с использованием Criteria API в Hibernate для программы из предыдущего задания. Добавить эндпоинты для каждой фильтрации.
18. ПРАКТИЧЕСКАЯ РАБОТА № 18
Цель работы
Знакомство с репозиториями и сервисами, реализация в проекте.
Взаимодействие с Spring Data JPA.
18.1. Теоретическая часть
Самой простой, но при этом достаточно эффективной архитектурой является слоистая архитектура, при которой приложение делится на отдельные независимые слои. Чаще всего используется 3 слоя:
1) слой представлений – в Spring реализуется с использованием контроллеров;
2) слой бизнес-логики (сервисов) – реализуется с помощью сервисов;
3) слой данных – реализуется с помощью репозиториев.
Сервисы и репозитории
Сервис – специальный слой, в котором хранится вся бизнес-логика. В данном слое должно быть как можно меньше взаимодействия со сторонними библиотеками для большей поддерживаемости. Для данных классов лучше использовать аннотацию Service. Она ничем не отличается от Component, просто для лучшей выразительности лучше пользоваться ей.
Также наиболее верно использовать интерфейсы на стыке между слоями. Зачем? Это паттерн – инверсия зависимости, для того, чтобы верхние слои не зависели от нижних, и можно было с легкостью подменить одну имплементацию на другую. Поэтому наиболее правильный способ создания репозитория – определение интерфейса, а затем его имплементации с аннотацией Service. Пример: public interface UserService {
List
}
@Service public class UserServiceImpl implements UserService {