Методичка java. Методичка По Шаблонам Java. Практикум для студентов, обучающихся по направлению подготовки 09. 03. 04 Программная инженерия
Скачать 1.94 Mb.
|
14.2. Задание Создать отдельный репозиторий 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. Создать классы-контроллеры для создания, удаления объектов и получения всех объектов каждого типа. Сами объекты хранить в памяти. 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 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; } Готово – теперь при сохранении пользователя при отсутствии у него 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 вместо памяти компьютера. 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 для программы из предыдущего задания. Добавить эндпоинты для каждой фильтрации. 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 { public List //code } public void saveOrUpdate(User user){ //code } } Репозитории тоже имеют свою аннотацию – Repository. Но также у Spring есть еще одна интереснейшая технология – Spring Data. Рассмотрим данную технологию на Spring Data JPA, так как она используется для реляционных баз данных. Данная технология основывается как раз-таки на репозиториях, но очень сильно уменьшает количество boilerplate кода. Все, что требуется сделать, это сконфигурировать взаимодействие с базой данных, создать интерфейс репозитория для каждой сущности – и все, можно дальше работать с бизнес-логикой, изредка дополняя методами в интерфейсах для нужного взаимодействия. Для начала возьмем приложение из предыдущего задания, и уберем предыдущую конфигурацию, перенеся полностью конфигурацию в application.yml: spring: datasource: url: your_url username: your_username password: your_password И добавим над конфигурацией аннотацию EnableJpaRepositories: @Configuration @EnableJpaRepositories public class AppConfig { } С конфигурацией покончено. Теперь создадим интерфейсы- репозитории, а также добавим возможность поиска по названию породы в Dog: public interface UserRepository extends JpaRepository } public interface DogRepository extends JpaRepository List } И все. Spring по названию метода определяет то, каким должен быть запрос. Например, напишем метод по поиску по породе и имени: public interface DogRepository extends JpaRepository List List } Достаточно просто и удобно. При этом все взаимодействие с БД передается Spring, и не приходится постоянно размышлять о создании сессии, написании HQL даже для простых запросов и т.д. Если все же нужно выполнить более сложный запрос, который не может сделать Spring, или нативный запрос, можно воспользоваться аннотацией Query: @Query(value = "select dogs.* from dogs join users on users.id = dogs.user_id where users.first_name = :username", nativeQuery = true) List Теперь остается лишь воспользоваться Dependency injection (DI) и получить бин данного интерфейса. Spring сам создает имплементацию интерфейса и наполняет нужной логикой. Также JpaRepository имеет стандартные методы, такие как findAll, так что не придется их даже объявлять: @Service @RequiredArgsConstructor public class DogService { private final DogRepository dogRepository; public User getUserByDog(Long dogId) { return dogRepository.findById(dogId).orElseThrow(() -> new IllegalStateException("Dog with this id not found")).getUser(); } public List } } Рекомендуется ознакомиться с документацией Spring для лучшего понимания репозиториев (https://docs.spring.io/spring- data/jpa/docs/2.2.10.RELEASE/reference/html/#reference). Spring Data repository – отличная технология для уменьшения boilerplate кода. |