В папке DOC sql-скрипты и др. полезные файлы.
Док. (ссылки) для изучения:
- Spring Boot Reference Documentation ;
- Spring Framework 6.1.5 Documentation ;
- Spring Framework 3.2.x Reference Documentation ;
- Getting Started Guides ;
- Developing with Spring Boot ;
Для начала проведем предварительную подготовку (первые 3-и шага из предыдущих частей):
Шаг 1. - в файле build.gradle добавим необходимые plugin-ы:
/*
Плагин Spring Boot добавляет необходимые задачи в Gradle
и имеет обширную взаимосвязь с другими plugin-ами.
*/
id 'org.springframework.boot' version '3.1.3'
/*
Менеджер зависимостей позволяет решать проблемы несовместимости
различных версий и модулей Spring-а
*/
id "io.spring.dependency-management" version '1.0.11.RELEASE'
/* Подключим Lombok */
id "io.freefair.lombok" version "8.3"
Шаг 2. - подключаем Spring Boot starter:
/*
Подключим Spring Boot Starter он включает поддержку
авто-конфигурации, логирование и YAML
*/
implementation 'org.springframework.boot:spring-boot-starter'
Шаг 3. - подключаем блок тестирования (Spring Boot Starter Test) (он будет активен на этапе тестирования):
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Шаг 4. - автоматически Gradle создал тестовую зависимость на Junit5 (мы можем использовать как Junit4, так и TestNG):
test {
useJUnitPlatform()
}
Шаг 5. - подключим блок для работы с БД (Spring Boot Starter Data Jpa):
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
!!! НЕ ЗАБЫВАЕМ !!! У нас есть классы (см. ConnectionPool.java и комментарии), где мы пытаемся внедрить параметры в
поля через аннотации, с использованием аннатационного же конструктора @RequiredArgsConstructor. Фокус не пройдет без
создания и настройки файла конфигурации: lombok.config - 'контекст' просто завалится.
Либо все делаем руками от начала и до конца, либо помним какие вспомогательные средства используем и какие их особенности
могут повлиять на работу приложения.
Шаг 6. - Для использования средств подобных Hibernate ENVERS подключим такую же поддержку от Spring (начиная с Lesson_59):
implementation 'org.springframework.data:spring-data-envers'
Еще раз повторим что такое Spring Data - DOC/SpringData.txt
Lesson 47 - Repository.
Spring Data JPA предоставляет кроме авто-конфигурации и управления транзакциями еще и автоматическое предоставление DAO-слоя через интерфейс Repository.
Repository - это интерфейс-маркер центрального репозитория. Он не имеет методов и говорит нам о том, что некие классы, в нашем случае это AutoConfiguration при встрече с Repository Interface будет обрабатывать его особым образом. В данном случае это создание слоя Repository.
И так, Repository параметризирован (захватывает) тип области (сущности), которой нужно управлять, а также тип идентификатора той самой области (сущности, т.е. в нашем случае см. CompanyRepository.java - у нас есть сущность Company и у нее есть ее - ID). Общая цель - хранить информацию о типе, а также иметь возможность обнаруживать интерфейсы, расширяющие этот интерфейс, во время сканирования пути к классам для упрощения создания bean-компонентов Spring.
Репозитории области, расширяющие этот интерфейс, могут выборочно предоставлять методы CRUD, просто объявляя методы с той же сигнатурой, что и те, которые объявлены в CrudRepository.
Пакет: org.springframework.data.repository
Interface Repository<T,ID>
Типы параметров:
T - тип области управляемой репозиторием (у нас, например: Company, User и т.д.);
ID - тип ID управляемой репозиторием области (у нас, например, ID Company - Integer);
Теперь перепишем наш CompanyRepository.java, который стал интерфейсом.
Фактически реализацию всех его методов берет на себя Spring. И тогда нам уже не нужен наш собственный CrudRepository.java - его мы удаляем и все упоминания (зависимости от) о нем в других классах (тестах).
Чтобы проверить наш новый CompanyRepository и как он работает, создадим тестовый метод deleteCompanyTest() в CompanyRepositoryTest.java (см. комментарии).
Всей этой магией занимается JpaRepositoriesAutoConfiguration, который создан, для того чтобы искать все интерфейсы (классы) Repository и на их основании создавать конкретные прокси реализации.
/* Автоматическая настройка репозиториев JPA Spring Data.
Активируется, когда в контексте настроен bean-компонент типа DataSource,
тип Spring Data JPA JpaRepository находится в пути к классам и нет другого
существующего настроенного JpaRepository.
После вступления в силу, автоматическая настройка эквивалентна включению
репозиториев JPA с использованием аннотации - @EnableJpaRepositories.
*/
@AutoConfiguration(after = { HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class })
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class })
/* Настройки который мы можем менять */
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
@Import(JpaRepositoriesImportSelector.class)
public class JpaRepositoriesAutoConfiguration {
/* code */
}
Поскольку мы используем Spring Boot, то и не используем явно аннотацию @EnableJpaRepositories.
На основании авто-конфигурации создание Bean зависимостей происходит в AbstractRepositoryConfigurationSourceSupport в котором есть нужные методы registerBeanDefinitions(). И как уже ранее описывалось все bean-ы в цикле проходят процесс создания и внедрения всех нужных зависимостей.
Несложно догадаться, что работающие 'пустые' методы работают не просто так, весь код реализуется Spring-ом через прокси объекты с использованием элементов АОП - аспектно ориентированного программирования.
Док. для изучения:
- Пакет org.springframework.data.repository ;
- Working with Spring Data Repositories ;
- Class JpaRepositoriesAutoConfiguration ;
- Class AbstractRepositoryConfigurationSourceSupport ;
Lesson 48 - Создание запросов к базе данных исходя из имен методов.
Принципы работы при создании имен методов эквивалентных генерируемым запросам к БД в наших репозиториях описан в DOC/SpringDataJPATutorial/6_QueriesFromMethodNames.txt, и естественно в официальной документации к Spring-у: JPA Repositories Query methods.
- MyCompanyRepository.java - пример репозитория использующего имена методов для формирования запросов к БД.
- MyCompanyRepositoryTest.java - интеграционный тест для проверки работы запросов нашего репозитория.
Результат теста, а точнее то, что мы увидим в консоли зависит от настроек запроса к БД. Если наша сущность Company получит именованный запрос с именем один в один, как в методе репозитория см. ниже, то вид самих запросов сгенерированных Hibernate изменятся - можно на время закомментировать аннотацию @NamedQuery в сущности Company.
См. док.:
Стратегия генерации запросов описанная выше, проста, изящна, но имеет следующие недостатки:
- Особенности парсера имен методов определяют, какие запросы мы можем создавать. Если анализатор имени метода не поддерживает необходимое ключевое слово, мы не сможем использовать эту стратегию.
- Имена методов сложных запросов длинные и некрасивые.
- Нет поддержки динамических запросов.
См. DOC/SpringDataJPATutorial/6_QueriesFromMethodNames.txt
Применение именованных запросов тоже не лишено недостатков, но это еще один способ создания запросов к БД. См. DOC/SpringDataJPATutorial/8_QueriesWithNamedQueries.txt
При этом мы помним, что Spring JPA используем весь функционал Hibernate, а значит мы можем им воспользоваться.
Особенность именованных запросов в том что они имеют преимущество перед PartTreeJpaQuery, рассмотренные выше, т.е. если у двух запросов будет одинаковое название, то первым в работу пойдет именованный запрос (NamedQuery).
Внесем изменения в нашу сущность Company см. Company.java - добавим аннотацию @NamedQuery и настроим ее.
Поскольку мы внесли изменения в сущность Company, то при запуске тестов, а они точно такие же что и в предыдущем задании отработают, но вид запросов Hibernate будет другим в части запроса 'findCompanyByName':
Hibernate:
select
c1_0.id,
c1_0.name
from
company c1_0
where
lower(c1_0.name)=lower(?)
Еще пример именованных запросов: Hibernate_part_4/src/main/java/oldboy/lesson_19/entity_19/Employee.java
В сущности Company в запросе 'findCompanyByNameWithParam' и соответственно у одноименного запроса в MyCompanyRepository.java применяется аннотация @Param.
Создадим копию нашего CompanyRepository и назовем его SecondCompanyRepository.java в нем мы применим аннотацию с прописанным в ней HQL запросом:
@Query("select c from Company c " +
"join fetch c.locales cl " +
"where c.name = :name")
Данная аннотация над методом *.findCompanyByName(String name), который может работать на основе генерации запроса по его имени или за счет именованного запроса, сразу захватывает приоритет и запрос внутри нее выполняется безоговорочно, даже если имя запроса в репозитории совпадает с именем именованного запроса в классе, в нашем случае Company.
-
SecondCompanyRepositoryTest.java - тестовый класс для проверки методов репозитория SecondCompanyRepository.java.
-
UserRepository.java - переписали наш класс в интерфейс и расширили JpaRepository, именно в нем мы еще раз применим аннотацию @Query и распишем HQL и нативные SQL запросы:
@Query(value = "SELECT u.* FROM users u WHERE u.username = :username", nativeQuery = true)
Еще больше примеров и разъяснений cм. DOC/SpringDataJPATutorial/7_QueriesWithAnnotation.txt
Lesson 51 - Модификация данных через запросы и использование аннотации @Modifying.
И так мы внесли изменения в UserRepository.java, как и в примерах выше сделали из него интерфейс и расширили JpaRepository<User, Long>, но если в первом случае мы в качестве сущности управления использовали Company - JpaRepository<Company, Integer> и ID - Integer, в случае с User это ID - Long, см. DOC/RepositoryInterfaceAndClass/JpaRepository.txt
-
UserRepository.java - интерфейс в котором мы опробуем функционал модификации данных в БД на примере метода и аннотаций:
@Modifying @Query("update User u " + "set u.role = :role " + "where u.id in (:ids)") int updateRole(Role role, Long... ids);
Более подробные комментарии см. внутри самого репозитория. В данном случае применялась аннотация @Modifying без параметров. В простых случаях этого обычно достаточно, однако если у нас возникнет, например, потребность в проверке измененных данных возникают некие затруднения при работе с персистентным контекстом, которые мы попытались обойти в следующем варианте данного метода во второй версии нашего репозитория SecondUserRepository.java.
-
SecondUserRepository.java - интерфейс в котором аннотация @Modifying метода *.updateRole(Role role, Long... ids) приобретает два параметра см. DOC/ModifyingAnnotationInterface.txt, что позволяет нам в некоторой степени управлять состоянием персистивного контекста и избежать большинства исключений:
@Modifying(clearAutomatically = true, flushAutomatically = true) @Query("update User u " + "set u.role = :role " + "where u.id in (:ids)") int updateRole(Role role, Long... ids);
Тестовые классы с пояснениями в комментариях:
См. док.:
Lesson 52 - Специальные параметры в запросах, интерфейс Pageable и класс Sort.
При создании запросов способом парсинга названия метода самого запроса, для опоры при составлении имени метода, приходится использовать две важных таблицы, из документации по Spring Data JPA - Reference Documentation:
- Таблица 4. Поддерживаемые ключевые слова внутри имен методов, раздел 4.3.2. Создание запроса ;
- Таблица 8. Ключевые слова запроса, приложение C. Ключевые слова запроса к репозиторию ;
Т.е. мы должны использовать некие легко обрабатываемые регулярными выражениями ключевые слова. И естественно, мы можем в названиях запросов передавать еще и параметры:
-
Например, мы хотим найти первый элемент из нашей выборки (в зависимости от БД мы можем использовать ключевые слова First или Top), а также хотим отсортировать нашу выборку (OrderBy) по убыванию (Desc), и тогда, название запроса будет выглядеть как-то так - findTopByOrderByIdDesc, см. SecondUserRepository.java и тестовый метод UserRepositoryThirdTest.java
-
Например, мы хотим получить уже не один результат запроса, а коллекцию из нескольких значений на наш запрос, допустим найти первые три User сущности, у которых день рождения был ранее некой даты - findTop3ByBirthDateBefore, далее мы снова хотим отсортировать набор данных по-убыванию - OrderByBirthDateDesc. И мы получаем очень длинное имя *.findTop3ByBirthDateBeforeOrderByBirthDateDesc(LocalDate birthDate), которое, в принципе, легко читается и примерно понятно, что должен делать данный метод, но хочется сделать его название короче. В параметре метода мы передаем ограничивающую запрос дату. И в консоли мы видим:
Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 where u1_0.birth_date<? /* ? - переданный параметр даты */ order by u1_0.birth_date desc fetch first ? rows only
-
Естественно при таких запросах нам бы хотелось придать некую динамику нашему запросу, чтобы каждый раз не писать новый метод под новый запрос. Например, в нашем предыдущем запросе мы могли бы передать в метод не один, а два параметра, одним из которых будет параметр класса Sort (или см. DOC/SliceSortAndPageUsing/SortClass.txt). Это параметр будет определять, каким образом мы будем обрабатывать результат нашего запроса. Перепишем наш предыдущий метод и добавим ключевые параметры: List findTop2ByBirthDateBefore(LocalDate birthDate, Sort sort). В тестовом методе *.checkDynamicSortTest() мы используем два варианта создания объектов данного класса, напрямую:
Sort sortById = Sort.by("id").and(Sort.by("firstname").and(Sort.by("lastname")));
А так же с использованием вложенного класса:
Sort.TypedSort<User> sortBy = Sort.sort(User.class);
Sort sort = sortBy.by(User::getFirstname).and(sortBy.by(User::getLastname));
- Наконец, есть интерфейс Pageable (или см. DOC/SliceSortAndPageUsing/PageableInterface.txt), который позволяет работать с выборками, как со
страницами (отсортированными). У данного интерфейса есть реализующий класс PageRequest, который более удобен при работе
со страницами (см. DOC/SliceSortAndPageUsing/PageRequestClass.txt).
- List findAllUserBy(Pageable pageable) - метод использующий объект интерфейса Pageable (см. DOC/SliceSortAndPageUsing/PageableInterface.txt) в качестве параметра в интерфейсе SecondUserRepository.java;
- void checkPageableTest() - метод тестирующий *.findAllUserBy(Pageable pageable) и применяющий реализацию интерфейса Pageable - PageRequest (см. DOC/SliceSortAndPageUsing/PageRequestClass.txt);
См. док.:
-
FourthUserRepository.java - интерфейс репозитория демонстрирующий работу Slice и Pageable. Возьмем одноименный метод из прошлого урока *.findAllUserBy(Pageable pageable) и, вместо List, вернем в нем Slice. Проверим результат в тестовом методе класса ForthUserRepositoryTest.java.
/* Нужно помнить, что, хотя, у нас в БД 5-ть записей, но запрос PageRequest делается с 1-ой стр. (не с 0 - ой), поэтому результаты Slice на экран идут начиная с 3-го ID. Сортировка прямая, т.е. по возрастанию ID. */ @Test void checkSliceTest() { PageRequest myPageable = PageRequest.of(1, 2, Sort.by("id")); Slice<User> myFirstSlice = fourthUserRepository.findAllUserBy(myPageable); while (myFirstSlice.hasNext()) { myFirstSlice.forEach(user -> System.out.println(user.getId())); myFirstSlice = fourthUserRepository.findAllUserBy(myFirstSlice.nextPageable()); myFirstSlice.forEach(user -> System.out.println(user.getId())); } } /* На экране результат работы демо-теста, сначала 1-ый Slice, затем 2-ой */ Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 order by u1_0.id offset ? rows fetch first ? rows only 3 4 Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 order by u1_0.id offset ? rows fetch first ? rows only 5
-
У объектов интерфейса Slice масса преимуществ, однако при работе с сайтами (и не только), нам очень часто бывает нужна т.н. пагинация - вывод необходимых элементов запроса на отдельной странице (страницах), при этом с возможностью подсчета количества выводимых страниц (их общего числа). В данном случае нам поможет интерфейс Page, наследник Slice, а значит он имеет все достоинства родителя и так же привносит свой функционал (см. DOC/SliceSortAndPageUsing/PageInterface.txt).
-
Применим интерфейс Page в методах *.findAllUserPagesBy() интерфейса FourthUserRepository и *.checkPaginationTest() тестового класса ForthUserRepositoryTest см. результаты запросов к БД:
Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 order by u1_0.id offset ? rows fetch first ? rows only Hibernate: select count(u1_0.id) from users u1_0 1 2 Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 order by u1_0.id offset ? rows fetch first ? rows only Hibernate: select count(u1_0.id) from users u1_0 3 4 Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 order by u1_0.id offset ? rows fetch first ? rows only 5
Очень просто обнаружить дополнительный запрос с подсчетом - count, который в свою очередь мы можем также скорректировать при помощи @Query (см. FourthUserRepository.java метод *.findAllUserPagesWithCountBy()):
@Query(value = "select u from User u",
countQuery = "select count(distinct u.firstname) from User u")
Page<User> findAllUserPagesWithCountBy(Pageable pageable);
В структуру кода тестового метода *.checkPaginationWithQueryCountTest() мы не вносим ни каких изменений в сравнении с *.checkPaginationTest() кроме использования аннотированного метода *.findAllUserPagesWithCountBy(Pageable pageable) и в консоли мы видим изменения дополнительных запросов к БД:
select
count(distinct u1_0.firstname)
from
users u1_0
Lesson 54 - Аннотация @EntityGraph в запроса репозиториев.
Тут мы рассмотрим вкратце методики применения аннотаций @EntityGraph и @NamedEntityGraph. Как бы мы не старались написать код приложения наиболее компактно, проблема N+1 при формировании запросов к БД остается. Поэтому, как и при изучении Hibernate Hibernate_part_7 по этому вопросу, мы прибегнем к помощи EntityGraph.
- FifthUserRepository.java - репозиторий в котором мы применяем два способа работы с аннотацией @EntityGraph, это именованный @NamedEntityGraph (см. код и комментарии в User.java) - метод *.findAllUserWithNamedEntityGraphBy(), и атрибуты аннотации @EntityGraph - метод *.findAllUserWithAttributeEntityGraphBy();
- FifthUserRepositoryTest.java - тестовый класс для изучения поведения обоих выше описанных метода репозитория;
!!! Внимание !!! При использовании сущностных графов @EntityGraph над методами репозитория, когда нам важна пагинация и подсчет страниц, мы получим проблемы чисто технического плана, поскольку будет происходить разделение запросов для получения LAZY сущностей (т.е. для одной Page получим перегруз данными, например для одной Company мы получим две локали и т.д.) и наше приложение либо может работать неправильно, либо посыпется при отображении (возврате) данных.
Всегда когда мы используем картирование OneToMany или ManyToMany и пытаемся воспользоваться Page нужно помнить о возможных проблемах с получением и отображением данных.
Lesson 55 - Аннотации @Lock и @QueryHints в запросах репозиториев.
Вопросы локирования (блокировки) транзакций подробно рассмотрены в Hibernate_part_8.
- SixthUserRepository.java - демонстрационный репозиторий в котором мы применим аннотацию @Lock к методу List findTop3ByBirthDateBefore(LocalDate birthDate, Sort sort), в качестве параметров в аннотацию можно передать тип блокировки (см. описание DOC/LockModeAndQueryAnnotation/LockModeTypeEnum.txt). Применение различных типов блокировок более подробно рассмотрено в Hibernate_part_8.
!!! Помним, что !!! Оптимистические блокировки обрабатываются на уровне нашего приложения, в то время, как пессимистические блокировки обрабатываются (задаются) на уровне нашей БД, т.е. в наших запросах (в случае PostgreSQL: PESSIMISTIC_READ - select for share, PESSIMISTIC_WRITE - select for update).
-
SixthUserRepositoryTest.java - тестовый класс. Если мы применим к нашему методу параметр PESSIMISTIC_READ, то в консоли увидим запрос к БД с ключевым словосочетанием "for share":
Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 where u1_0.birth_date<? order by u1_0.lastname, u1_0.lastname fetch first ? rows only for share
Для того чтобы разобраться, какие блокировки относятся к 'for share' или 'for update' см. документацию к своей БД (PostgreSQL).
Пример применения аннотации @QueryHints приведен в том же тесте SixthUserRepository, краткая документация см. DOC/Hint.
См. док.:
Lesson 56 - Hibernate (проекция, DTO) Projection.
Для разъяснения понятия проекция (projection) можно изучить статьи из см. DOC/ArticleAboutProjection, понятие DTO - MVCPractice/DOC/DTO.txt и MVCPractice/DOC/DTO_example.png
-
PersonalInfo.java - простая DTO проекция, неизменяемая, т.к. нам нужно просто получить информацию из БД. Содержит имя, фамилию и дату рождения. Метод List findAllByCompanyId(Integer companyId) в ProjectionUserRepository.java;
-
PersonalRole.java - простая DTO проекция, созданная для демонстрации работы динамических проекций запросов, чуть отличается от предыдущей, описание принципа см. DOC/ArticleAboutProjection/SpringDataJPAProjections.txt. Метод List findAllByCompanyId(Integer companyId, Class type) в ProjectionUserRepository.java;
-
PersonalInfoTwo.java - проекция через интерфейс (см. открытые и закрытые проекции в DOC/ArticleAboutProjection/SpringDataJPAProjections.txt), в интерфейсе будут методы возвращающие необходимые нам значения полей сущностей через геттеры. Важным моментом в данном случае будет жесткое соответствие имен геттеров и имен полей сущностей в ResultSet.
-
UserRepositoryProjectionsTest.java - тестовый класс для демонстрации работы наших проекционных методов см. комментарии.
Lesson 57 - Spring Custom Repository Implementation - Самописные репозитории.
В статьях DOC/SpringDataJPATutorial/14_AddCustomMethodsToSingleRepository.txt и DOC/SpringDataJPATutorial/15_AddCustomMethodsToAllRepositories.txt вкратце описано, каким образом мы можем создавать и интегрировать наши собственные кастомные (самописные) репозитории и методы в наш проект.
Рассмотрим как это реализовано у нас:
-
Шаг 1. - Создадим UserFilterDto.java - некая проекция нашей сущности User;
-
Шаг 2. - Создадим интерфейс FilterUserRepository.java - свою кастомную имплементацию репозитория (для работы со всем доступным из JPA и Hibernate инструментарием), и создадим в нем метод (используем доступную из Spring-a именование по ключевым словам), принимающий параметр в качестве фильтра и возвращающий список User-ов подпадающих под переданный фильтр;
-
Шаг 3. - Создаем класс FilterUserRepositoryImpl.java, реализующий наш кастомный интерфейс и переопределяющий его метод;
-
Шаг 4. - Указываем Spring-у, что он должен использовать нашу рукописную имплементацию при обращении к UserRepository, для этого он должен расширять наш рукописный репозиторий, т.е.
@Repository public interface UserRepository extends JpaRepository<User, Long>, FilterUserRepository { /* method code */ }
В данном случае Spring при обращении к UserRepository и методу *.findAllByFilter(UserFilterDto filter) будет искать имплементацию его родителя FilterUserRepository с постфиксом Impl (вот почему важно следовать декларации имен см. интерфейс EnableJpaRepositories метод *.repositoryImplementationPostfix()).
-
Шаг 5. - Напишем тест для нашего кастомного (самописного) метода - CustomUserRepositoryTest.java и проверим его работоспособность, см. вывод Hibernate запроса к БД на экран:
Hibernate: select u1_0.id, u1_0.birth_date, u1_0.company_id, u1_0.firstname, u1_0.lastname, u1_0.role, u1_0.username from users u1_0 where u1_0.lastname like ? escape '' and u1_0.birth_date<?
И так, мы прибегли к помощи Criteria API, но при этом спокойно обратились к репозиторию и получили нужный нам результат, т.е. мы можем, при необходимости, писать еще более сложные запросы используя и другие методы создания запросов к БД.
Lesson 58 - Spring JPA Auditing - Аудит работы нашего приложения.
Вопросы аудита наших записей в БД средствами Spring рассмотрен в статьях: DOC/SpringDataJPATutorial/13_1_AuditingSpringDataJPA.txt и DOC/SpringDataJPATutorial/13_2_AuditingSpringDataJPA.txt. Аудит средствами Hibernate рассмотрены в Hibernate_part_10. Но в сравнении с Hibernate, Spring очень сильно упростил нам жизнь.
Реализуем аудит в нашем приложении (будем отслеживать изменения в записях User):
- Шаг 1. - Создадим аудирующую (фиксирующую изменения) сущность AuditingEntity.java, это абстрактный класс, все его
наследники, смогут использовать его поля для фиксации изменений (создание и модификация, кем и когда), для этого:
- Шаг 1.1 - Добавим поля класса Instant для когда (createdAt, modifiedAt) и для кто/кем (createdBy, modifiedBy) класса String, которые фиксируют соответствующие изменения в БД ;
- Шаг 1.2 - Либо прямым кодом, либо через аннотации @Getter и @Setter, создаем геттеры и сеттеры для полей;
- Шаг 1.3 - Для того чтобы наследники могли использовать поля родителя добавим аннотацию @MappedSuperclass;
- Шаг 1.4 - Для того чтобы мы фиксировали изменения в сущностях нам нужны слушатели (listeners), поэтому добавляем аннотацию @EntityListeners(AuditingEntityListener) в параметры которой передаем, не наш самописный слушатель, а уже готовый Spring-овый слушатель AuditingEntityListener;
- Шаг 1.5 - Указываем Spring-овому слушателю (listener-у) какие поля нужно обновлять, аннотируя их соответствующими аннотациями (@CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy) см. AuditingEntity.java;
- Шаг 2. - Поскольку наш класс аудита реализует нашу же BaseEntity, мы легко можем изменить класс User. Поскольку мы запланировали вести аудит его изменений, унаследуемся от AuditingEntity. И конечно, дабы избежать исключений добавим еще одно поле в аннотацию @EqualsAndHashCode(callSuper=false) в сущности User;
- Шаг 3. - Внесем соответствующие изменения в БД, добавим поля, которые будут фиксировать все изменения в записях сущностей User (см. SQL скрипты DOC/SQL_Update_Base_Table/UserTableUpdate.sql);
- Шаг 4. - Запускаем механизм аудирования изменений данных в БД. Для этого мы должны использовать аннотацию @EnableJpaAuditing, которой можем аннотировать, например основной класс нашего приложения SpringAppRunner.java (но мы этого делать не будем), либо создать свой класс конфигурации аудита и пометить данной аннотацией его, что и сделаем. Создаем AuditConfiguration.java и естественно аннотируем его еще и как @Configuration.
- Шаг 5. - Создаем метод провайдер данных *.auditorAware() для наших полей в классе конфигурации аудита AuditConfiguration, т.е. мы должны предоставить Spring-у BEAN интерфейса AuditorAware;
- Шаг 6. - Создаем тестовый класс UserAuditingTest.java и проверяем работу аудита;
См. док.:
- Пакет org.springframework.data.annotation ;
- Пакет org.springframework.data.jpa.domain.support ;
- Пакет javax.persistence ;
- Lombok Features ;
Lesson 59 - Hibernate-Envers в Spring приложении.
Как было описано выше, для данного раздела, подключим Spring зависимость, чтобы использовать механизм Envers:
implementation 'org.springframework.data:spring-data-envers'
Для сравнения с Hibernate функционалом и возможностями см. Hibernate_part_10 и оф. документация (ENG) Hibernate ORM Envers или Hibernate Envers
Освежим в памяти, для чего нужен функционал Envers:
- Аудит всех сопоставлений, определенных спецификацией JPA.
- Аудит некоторых сопоставлений Hibernate, которые расширяют JPA, например, пользовательские типы и коллекции/карты «простых» типов, таких как строки, целые числа.
- Регистрация данных для каждой ревизии с использованием «объекта ревизии».
- Запрос исторических снимков объекта и его ассоциаций.
Для работы с механизмом отслеживания версий (ревизий) наших сущностей проделаем следующие шаги:
-
Шаг 1. - Создадим отслеживающую изменения версий сущность (некий, Git-подобный механизм) Revision.java, содержащих два поля: id - номер проведенного изменения и timestamp - время, когда было проведено изменение.
-
Шаг 2. - Над всеми сущностями, которые мы планируем отслеживать, мы должны проставить аннотацию @Audited, например, наша сущность User см. User.java.
-
Шаг 3. - Настраиваем (задаем) параметры отслеживания. В случае нашей сущности User мы не хотим отслеживать связные сущности, например Company, а так же любые коллекции внутри User. Поэтому в аннотацию @Audited передаем параметр targetAuditMode = RelationTargetAuditMode.NOT_AUDITED, а коллекцию List userChats - помечаем, как @NotAudited.
-
Шаг 4. - Необходимо создать в нашей БД соответствующие таблицы, которые будут отслеживать изменения аудируемой сущности. Настроим данный функционал на автоматическое создание. Поскольку мы будем проверять работоспособность кода в наших тестах, внесем необходимые изменения в resources/application-test.yml:
spring: jpa: properties.hibernate: hbm2ddl.auto: update
В данном случае при запуске теста, который вносит изменения в отслеживаемую сущность User, в нашей БД автоматически будет создано две таблицы: revision и users_aud. В них будет внесена информация об изменениях сущности см. БД.
- Шаг 5. - Запустим механизм аудирования. Для этого в файле авто-конфигурации нашего приложения AuditConfiguration.java добавим аннотацию @EnableEnversRepositories или см. DOC/DataEnvers/EnableEnversRepositories.txt. Передадим в параметры аннотации наш корневой пакет для сканирования отслеживаемых объектов: basePackageClasses = SpringAppRunner.class
- Шаг 6. - Для наглядности работы механизма Hibernate Envers, мы запустим тест и внесем изменения в БД, чтобы изменения были зафиксированы необходимо аннотировать тестовый метод, как @Commit (см. UserAuditingTest.java).
Фактически, реализовав данные шаги мы получим в нашей БД необходимые таблицы для каждой отслеживаемой сущности (у нас это User). Естественно мы не только фиксируем изменения, но и можем к ним обращаться, а так же откатывать внесенные изменения. Для этого мы не будем создавать новую сущность, а просто расширим наш уже существующий UserRepository, добавив еще один интерфейс - RevisionRepository, как расширяемый. И тогда нам будут доступны все методы данного интерфейса для работы с внесенными изменениями, т.е. с ревизиями см. DOC/DataEnvers/RevisionRepositoryInterface.txt.
- FindRevisionTest.java - тестовый класс для проверки работы методов полученных UserRepository при расширении RevisionRepository см. тестовый метод checkFindLastRevisionTest().
См. док.:
См. официальные Guides:
- Getting Started Guides - Эти руководства, рассчитанные на 15–30 минут, содержат быстрые практические инструкции по созданию «Hello World» для любой задачи разработки с помощью Spring. В большинстве случаев единственными необходимыми требованиями являются JDK и текстовый редактор.
- Topical Guides - Тематические руководства предназначенные для прочтения и понимания за час или меньше, содержит более широкий или субъективный контент, чем руководство по началу работы.
- Tutorials - Эти учебники, рассчитанные на 2–3 часа, обеспечивают более глубокое контекстное изучение тем разработки корпоративных приложений, что позволяет вам подготовиться к внедрению реальных решений.
-
Справочное руководство Spring Framework: 12.5.6 Использование @Transactional
-
Справочное руководство Spring Framework: 14.3.1 Источник данных
-
Справочное руководство Spring Framework: 15.5.1 Три варианта установки JPA в среде Spring
-
Справочное руководство Spring Framework: 15.5.3 Управление транзакциями
-
Справочное руководство Spring Framework: 28.4.3 Аннотация @Async
-
Справочная документация Hibernate: 6.4 Пользовательские типы
-
Справочное руководство Hibernate ORM: 3.4. Дополнительные свойства конфигурации
-
Справочное руководство Spring Data JPA: 3.3 Определение интерфейсов репозитория
-
Справочное руководство Spring Data JPA: 3.3.1 Определение точной настройки репозитория
-
Справочное руководство Spring Data JPA: 3.4.2 Создание запроса
-
Справочное руководство Spring Data JPA: 3.4.3 Выражения свойств
-
Справочное руководство Spring Data JPA: 3.4.4 Обработка специальных параметров
-
Справочное руководство Spring Data JPA: 3.4.5 Ограничение результатов запроса
-
Справочное руководство Spring Data JPA: 3.4.6 Потоковая передача результатов запроса
-
Справочное руководство Spring Data JPA: 3.5 Создание экземпляров репозитория
-
Справочное руководство Spring Data JPA: 4.3.5 Использование именованных параметров
-
Что нового в Spring Data Dijkstra — асинхронный вызов метода репозитория
-
Что нового в Spring Data Dijkstra — поддержка типов-оболочек в качестве возвращаемых значений
-
Код, раскрывающий намерения, с новым типом Java 8 'Optional'
-
Устали от NullPointerExceptions? Рассмотрите возможность использования Java SE 8 'Optional'
-
Дизайн с использованием Optional (статья удалена, интересный блог)