Spring DATA
Миссией Spring DATA является предоставление единой модели программирования с использованием Spring для доступа к данным, сохраняя при этом специальные черты базового хранилища.
Фреймворк позволяет облегчить использование технологий доступа к данным, реляционных и не реляционных баз данных, облачных баз данных. Spring DATA — базовый проект, который включает множество других подпроектов, которые работают с конкретными базами данных.
Общий раздел Spring Data JPA
Java Persistence API (JPA)
Спецификация JPA 2.1 была выпущена 22 апреля 2013 года. Входит в J2EE и является стандартом доступа к данным в JAVA. Поддерживает большинство популярных поставщиков службы постоянства, например Hibernate.
Описание и основные возможности JPA
- JPA — пример приложения Hello World. Настройка использования Spring + JPA + Hibernate. Часть 1 — Описание стандарта Java Persistence API (JPA)
- JPA — пример приложения Hello World. Настройка использования Spring + JPA + Hibernate. Часть 2
Создание запросов в JPA, использование Java Persistence Query Language (JPQL)
- JPA — создание нетипизированных запросов и запросов со специальным типом
- JPA – операции INSERT, UPDATE, DELETE. Примеры запросов вставки, удаления, обновления на JPQL
- JPA – создание собственного запроса на чистом SQL
Создание запросов с использованием API-интерфейса критериев JPA 2
Spring DATA JPA
Базовые настройки для запуска приложения с использованием Spring Data JPA
Аудит изменений в классе сущности в Spring Data JPA
Spring Data JPA + Hibernate Envers
Вопросы для прохождения интервью на позицию Java разработчик
- Собеседование по Java EE — Spring Framework (вопросы и ответы). Часть 1
- Собеседование по Java EE — Spring Framework (вопросы и ответы). Часть 2
Модули Spring: Spring Data
Spring Data — модуль, который обеспечивает программным приложениям доступ к данным посредством реляционных и нереляционных баз данных, облачных сервисов и фреймворков map-reduce. Какими же характеристиками он обладает?
Прежде всего, Spring Data включает в себя много подпроектов, которые предназначены для определенных СУБД, таких как MySQL, Redis, MongoDB и пр. Кроме того, есть возможность использовать подмодули, которые разработаны сообществом Spring для более специализированных БД типа ArangoDB, Microsoft Azure Cosmos DB, Google Datastore и других.
Главный механизм, который реализован в Spring Data, — это репозиторий. Речь идет о наборе интерфейсов, применяющих JPA Entity при взаимодействии с данными.
Основные характеристики следующие:
- Настраиваемое отображение сущностей в базах данных на объекты Java.
- Возможность создания динамических запросов в БД посредством сигнатуры метода интерфейса репозитория.
- Базовые классы для разных задач.
- Прозрачный аудит объектов.
- Есть возможность интеграции своего кода репозитория.
- Несложная интеграция со Spring посредством JavaConfig и кастомных пространств имен XML.
- Интеграция с контроллерами Spring MVC является расширенной.
В каких целях используется Spring Data?
Этот модуль пригодится везде, где требуется обеспечить доступ к данным. К тому же, Spring Data относительно просто интегрируется с прочими модулями Spring.
Если хотите узнать о модулях Spring Data больше, вот ссылка на официальную документацию. А если хотите научиться «выживать» в проектах, где есть «Спринг», добро пожаловать на специализированный курс в Otus!
По материалам tproger.ru.
Spring Data JPA
Spring Data — дополнительный удобный механизм для взаимодействия с сущностями базы данных, организации их в репозитории, извлечение данных, изменение, в каких то случаях для этого будет достаточно объявить интерфейс и метод в нем, без имплементации.
- Spring Repository
- Методы запросов из имени метода
- Конфигурация и настройка
- Специальная обработка параметров
- Пользовательские реализации для репозитория
- Пользовательский Базовый Репозиторий
- Методы запросов — Query
1. Spring Repository
Основное понятие в Spring Data — это репозиторий. Это несколько интерфейсов которые используют JPA Entity для взаимодействия с ней. Так например интерфейс
public interface CrudRepository extends Repository
обеспечивает основные операции по поиску, сохранения, удалению данных (CRUD операции)
T save(T entity); Optional findById(ID primaryKey); void delete(T entity);
Есть и другие абстракции, например PagingAndSortingRepository.
Т.е. если того перечня что предоставляет интерфейс достаточно для взаимодействия с сущностью, то можно прямо расширить базовый интерфейс для своей сущности, дополнить его своими методами запросов и выполнять операции. Сейчас я покажу коротко те шаги что нужны для самого простого случая (не отвлекаясь пока на конфигурации, ORM, базу данных).
1. Создаем сущность
@Entity @Table(name = "EMPLOYEES") public class Employees < private Long employeeId; private String firstName; private String lastName; private String email; // . . .
2. Наследоваться от одного из интерфейсов Spring Data, например от CrudRepository
@Repository public interface CustomizedEmployeesCrudRepository extends CrudRepository
3. Использовать в клиенте (сервисе) новый интерфейс для операций с данными
@Service public class EmployeesDataService < @Autowired private CustomizedEmployeesCrudRepository employeesCrudRepository; @Transactional public void testEmployeesCrudRepository() < OptionalemployeesOptional = employeesCrudRepository.findById(127L); //. >
Здесь я воспользовался готовым методом findById. Т.е. вот так легко и быстро, без имплементации, получим готовый перечень операций из CrudRepository:
S save(S var1); Iterable saveAll(Iterable var1); Optional findById(ID var1); boolean existsById(ID var1); Iterable findAll(); Iterable findAllById(Iterable var1); long count(); void deleteById(ID var1); void delete(T var1); void deleteAll(Iterable var1); void deleteAll();
Понятно что этого перечня, скорее всего не хватит для взаимодействия с сущностью, и тут можно расширить свой интерфейс дополнительными методами запросов.
2. Методы запросов из имени метода
Запросы к сущности можно строить прямо из имени метода. Для этого используется механизм префиксов find…By, read…By, query…By, count…By, и get…By, далее от префикса метода начинает разбор остальной части. Вводное предложение может содержать дополнительные выражения, например, Distinct. Далее первый By действует как разделитель, чтобы указать начало фактических критериев. Можно определить условия для свойств сущностей и объединить их с помощью And и Or. Примеры
@Repository public interface CustomizedEmployeesCrudRepository extends CrudRepository < // искать по полям firstName And LastName OptionalfindByFirstNameAndLastName(String firstName, String lastName); // найти первые 5 по FirstName начинающихся с символов и сортировать по FirstName List findFirst5ByFirstNameStartsWithOrderByFirstName(String firstNameStartsWith);
В документации определен весь перечень, и правила написания метода. В качестве результата могут быть сущность T, Optional, List, Stream. В среде разработки, например в Idea, есть подсказка для написания методов запросов.
Достаточно только определить подобным образом метод, без имплементации и Spring подготовит запрос к сущности.
@SpringBootTest public class DemoSpringDataApplicationTests < @Autowired private CustomizedEmployeesCrudRepository employeesCrudRepository; @Test @Transactional public void testFindByFirstNameAndLastName() < OptionalemployeesOptional = employeesCrudRepository.findByFirstNameAndLastName("Alex", "Ivanov");
3. Конфигурация и настройка
Весь проект доступен на github
github DemoSpringData
Здесь лишь коснусь некоторых особенностей.
В context.xml определенны бины transactionManager, dataSource и entityManagerFactory. Важно указать в нем также
путь где определены репозитории.
EntityManagerFactory настроен на работу с Hibernate ORM, а он в свою очередь с БД Oracle XE, тут возможны и другие варианты, в context.xml все это видно. В pom файле есть все зависимости.
4. Специальная обработка параметров
В методах запросов, в их параметрах можно использовать специальные параметры Pageable, Sort, а также ограничения Top и First.
Например вот так можно взять вторую страницу (индекс с -0), размером в три элемента и сортировкой по firstName, предварительно указав в методе репозитория параметр Pageable, также будут использованы критерии из имени метода — «Искать по FirstName начиная с % „
@Repository public interface CustomizedEmployeesCrudRepository extends CrudRepository < ListfindByFirstNameStartsWith(String firstNameStartsWith, Pageable page); //. > // пример вызова @Test @Transactional public void testFindByFirstNameStartsWithOrderByFirstNamePage() < Listlist = employeesCrudRepository .findByFirstNameStartsWith("A", PageRequest.of(1,3, Sort.by("firstName"))); list.forEach(e -> System.out.println(e.getFirstName() + " " +e.getLastName())); >
5. Пользовательские реализации для репозитория
Предположим что в репозиторие нужен метод, который не получается описать именем метода, тогда можно реализовать с помощью своего интерфейса и класса его имплементирующего. В примере ниже добавлю в репозиторий метод получения сотрудников с максимальной оплатой труда.
public interface CustomizedEmployees < ListgetEmployeesMaxSalary(); >
Имплементирую интерфейс. С помощью HQL (SQL) получаю сотрудников с максимальной оплатой, возможны и другие реализации.
public class CustomizedEmployeesImpl implements CustomizedEmployees < @PersistenceContext private EntityManager em; @Override public List getEmployeesMaxSalary() < return em.createQuery("from Employees where salary = (select max(salary) from Employees )", Employees.class) .getResultList(); >>
А также расширяю Crud Repository Employees еще и CustomizedEmployees.
@Repository public interface CustomizedEmployeesCrudRepository extends CrudRepository, CustomizedEmployees
Здесь есть одна важная особенность. Класс имплементирующий интерфейс, должен заканчиваться (postfix) на Impl, или в конфигурации надо поставить свой postfix
Проверяем работу этого метода через репозиторий
public class DemoSpringDataApplicationTests < @Autowired private CustomizedEmployeesCrudRepository employeesCrudRepository; @Test @Transactional public void testMaxSalaryEmployees() < Listemployees = employeesCrudRepository.getEmployeesMaxSalary(); employees.stream() .forEach(e -> System.out.println(e.getFirstName() + " " + e.getLastName() + " " + e.getSalary())); >
Другой случай, когда надо изменить поведение уже существующего метода в интерфейсе Spring, например delete в CrudRepository, мне надо что бы вместо удаления из БД, выставлялся признак удаления. Техника точно такая же. Ниже пример:
public interface CustomizedEmployees < void delete(T entity); // . >// Имплементация CustomizedEmployees public class CustomizedEmployeesImpl implements CustomizedEmployees
Теперь если в employeesCrudRepository вызвать delete, то объект будет только помечен как удаленный.
6. Пользовательский Базовый Репозиторий
В предыдущем примере я показал как переопределить delete в Crud репозитории сущности, но если это надо делать для всех сущностей проекта, делать для каждой свой интерфейс как то не очень. тогда в Spring data можно настроить свой базовый репозиторий. Для этого:
Объявляется интерфейс и в нем метод для переопределения (или общий для всех сущностей проекта). Тут я еще для всех своих сущностей ввел свой интерфейс BaseEntity (это не обязательно), для удобства вызова общих методов, его методы совпадают с методами сущности.
public interface BaseEntity < Boolean getDeleted(); void setDeleted(Boolean deleted); >// Сущность Employees @Entity @Table(name = "EMPLOYEES") public class Employees implements BaseEntity < private Boolean deleted; @Override public Boolean getDeleted() < return deleted; >@Override public void setDeleted(Boolean deleted) < this.deleted = deleted; >// Базовый пользовательский интерфейс @NoRepositoryBean public interface BaseRepository extends JpaRepository < void delete(T entity); >//Базовый пользовательский класс имплементирующий BaseRepository public class BaseRepositoryImpl extends SimpleJpaRepository implements BaseRepository < private final EntityManager entityManager; public BaseRepositoryImpl(JpaEntityInformationentityInformation, EntityManager entityManager) < super(entityInformation, entityManager); this.entityManager = entityManager; >@Transactional @Override public void delete(BaseEntity entity) < entity.setDeleted(true); entityManager.persist(entity); >>
В конфигурации надо указать этот базовый репозиторий, он будет общий для всех репозиториев проекта
Теперь Employees Repository (и др.) надо расширять от BaseRepository и уже его использовать в клиенте.
public interface EmployeesBaseRepository extends BaseRepository < // . >
Проверяю работу EmployeesBaseRepository
public class DemoSpringDataApplicationTests < @Resource private EmployeesBaseRepository employeesBaseRepository; @Test @Transactional @Commit public void testBaseRepository() < Employees employees = new Employees(); employees.setLastName("Ivanov"); // Query by Example (QBE) Exampleexample = Example.of(employees); Optional employeesOptional = employeesBaseRepository.findOne(example); employeesOptional.ifPresent(employeesBaseRepository::delete); >
Теперь также как и ранее, объект будет помечен как удаленный, и это будет выполняться для всех сущностей, которые расширяют интерфейс BaseRepository. В примере был применен метод поиска — Query by Example (QBE), я не буду здесь его описывать, из примера видно что он делает, просто и удобно.
7. Методы запросов — Query
Ранее я писал, что если нужен специфичный метод или его реализация, которую нельзя описать через имя метода, то это можно сделать через некоторый Customized интерфейс ( CustomizedEmployees) и сделать реализацию вычисления. А можно пойти другим путем, через указание запроса (HQL или SQL), как вычислить данную функцию.
Для моего примера c getEmployeesMaxSalary, этот вариант реализации даже проще. Я еще усложню его входным параметром salary. Т.е. достаточно объявить в интерфейсе метод и запрос вычисления.
@Repository public interface CustomizedEmployeesCrudRepository extends CrudRepository, CustomizedEmployees < @Query("select e from Employees e where e.salary >:salary") List findEmployeesWithMoreThanSalary(@Param("salary") Long salary, Sort sort); // . >
@Test @Transactional public void testFindEmployeesWithMoreThanSalary() < Listemployees = employeesCrudRepository.findEmployeesWithMoreThanSalary(10000L, Sort.by("lastName"));
Упомяну лишь еще, что запросы могут быть и модифицирующие, для этого к ним добавляется еще аннотация @Modifying
@Modifying @Query("update Employees e set e.firstName = ?1 where e.employeeId = ?2") int setFirstnameFor(String firstName, String employeeId);
Еще одной из замечательных возможностей Query аннотации — это подстановка типа домена сущности в запрос по шаблону # , через SpEL выражения.
Так например в моем гипотетическом примере, когда мне надо для всех сущностей иметь признак “удален», я сделаю базовый интерфейс с методом получения списка объектов с признаком «удален» или «активный»
@NoRepositoryBean public interface ParentEntityRepository extends Repository < @Query("select t from #t where t.deleted = ?1") List findMarked(Boolean deleted); >
Далее все репозитории для сущностей можно расширять от него. Интерфейсы которые не являются репозиториями, но находятся в «base-package» папке конфигурации, надо аннотировать @NoRepositoryBean.
@Repository public interface EmployeesEntityRepository extends ParentEntityRepository
Теперь когда будет выполняться запрос, в тело запроса будет подставлено имя сущности T для конкретного репозитория который будет расширять ParentEntityRepository, в данном случае Employees.
@SpringBootTest public class DemoSpringDataApplicationTests < @Autowired private EmployeesEntityRepository employeesEntityRepository; @Test @Transactional public void testEntityName() < ListemployeesMarked = employeesEntityRepository.findMarked(true); // .
Объектно-реляционное отображение с помощью JPA Hibernate и Spring Data JPA Персистентность с помощью Spring Data JPA
В этом разделе мы напишем приложение Spring Data JPA, которое сохраняет запись в базе данных и затем извлекает ее. Для этого добавим зависимости Spring со стороны конфигурации Apache Maven.
Листинг 9. Зависимости Maven от Spring
Модуль spring-data-jpa обеспечивает поддержку репозитория для JPA и содержит транзитивные зависимости от других модулей, которые нам понадобятся, например spring-core и spring-context.
Кроме того, нам нужна зависимость spring-test для запуска тестов с помощью расширения Spring.
Стандартный файл конфигурации для Spring Data JPA — это Java класс для создания и настройки бинов, необходимых для Spring Data. Конфигурацию можно сделать с помощью XML файла или Java кода. Мы используем второй вариант. Создадим следующий файл конфигурации для персистентного приложения:
Листинг 10. Класс SpringDataConfiguration
- Аннотация @EnableJpaRepositories будет сканировать класс конфигурации для репозиториев Spring Data #1.
- Создаем бин источника данных #2 и указываем свойства JDBC - драйвер #3, URL базы данных #4, имя пользователя #5 и пароль #6 для доступа.
- Далее создаем бин диспетчера транзакций на основе фабрики диспетчеров классов-сущностей #7. Каждое взаимодействие с базой данных должно происходить в пределах транзакции, и поэтому для Spring Data нужен бин диспетчера транзакций.
- Создаем бин адаптера вендора JPA, который необходим для JPA, чтобы взаимодействовать с Hibernate #8. Настраиваем этот адаптер вендора для доступа к базе данных MySQL #9.
- Мы создаем объект, принадлежащий классу LocalContainerEntityManagerFactoryBean – это бин, который производит EntityManagerFactory в соответствии со контрактом начальной загрузки стандартного контейнера JPA #10.
- Настраиваем источник данных #11, адаптер вендора #12 и пакеты сканирования для классов сущностей #13. As the Item entity is located in cscs23.orm, we set this package to be scanned.
- Spring Data JPA обеспечивает поддержку для уровней доступа к данным на основе JPA за счет уменьшения объема стандартного кода и создания реализаций для интерфейсов репозитория. Нам нужно только определить интерфейс нашего собственного репозитория для расширения интерфейсов Spring Data.
Листинг 11. Интерфейс ItemRepository
Интерфейс ItemRepository расширяет CrudRepository. Это репозиторий сущностей Item с идентификатором Long. Помните, что класс Item имеет поле id с аннотацией @Id of type Long. Мы можем напрямую вызывать методы save, findAll или findById, унаследованные из CrudRepository, и можем использовать их без дополнительной информации для выполнения операций с базой данных.
Для дополнительных операций можно использовать интерфейс JpaRepository, который, в свою очередь, расширяет интерфейс CrudRepository. Он идет с дополнительными методами, включая в данном случае методы, которые позволяют выполнять разбиение на страницы, сортировку или пакетные операции.
Spring Data JPA использует шаблон proxy для поддержки механизма взаимодействия с базой данных. Целью proxy является замена другого объекта. Spring Data JPA создаст класс proxy, реализующий интерфейс ItemRepository, а также создаст его методы (рис. 1). Этот класс proxy будет определять все методы, унаследованные от CrudRepository, в том числе save, findAll или findById. Программисту больше не придется тратить время на написание логики этих методов — их реализация осуществляется со стороны Spring Data JPA. Это работает в духе фреймфорков и, в частности, Spring — происходит инверсия контроля, рабочий поток уже включен, обеспечиваются точки перехвата, которые программист должен заполнить, сосредоточившись на бизнес-логике.
Сохраняем запись Item в базу данных, используя Spring Data JPA.
Листинг 12. Класс ItemSpringDataJPATest
- Расширяем тест, используя SpringExtension #1. Это расширение используется для интеграции контекста тестирования Spring с тестированием JUnit 5 Jupiter.
- Контекст тестирования Spring настраивается с помощью бинов, определенных в ранее представленном классе SpringDataConfiguration #2.
- Бин ItemRepository внедряется с помощью Spring через auto-wiring #3. Это возможно, поскольку пакет cscs23.orm.repositories, в котором находится ItemRepository, используется в качестве аргумента аннотации @EnableJpaRepositories в листинге 10.
- Если мы хотим проверить и вызвать itemRepository.getClass(), то увидим, что это что-то вроде com.sun.proxy.$Proxy41 – proxy, сгенерированный Spring Data JPA, как показано на Рис. 1.
- Создаем новый экземпляр класса Item и устанавливаем его свойство info #4.
- Сохраняем объект item #5. Метод save унаследован от интерфейса CrudRepository, и его тело будет сгенерировано Spring Data JPA при создании класса proxy. Он просто сохранить сущность Item в базу данных.
- Мы опять имеет все преимущества работы с JPA или Hibernate: нет кода SQL и не нужен JDBC; нет операций CRUD внутри кода Java, только классы, объекты и методы; переносимость на каждый диалект SQL обеспечивается ORM.
- Кроме того, тест Spring Data JPA намного короче, чем тесты с использованием интерфейса JPA или Hibernate. Это возможно потому, что стандартный код был удален и больше нет необходимости явного создания объектов или явного контроля транзакции. Благодаря внедрению объекта из репозитория генерируются методы класса proxy. Основная нагрузка теперь на стороне конфигурации, но ее необходимо делать только один раз для каждого приложения.