Spring Data¶
Spring Data - проект экосистемы Spring, который убирает шаблонный код доступа к данным: реляционные базы, NoSQL, Redis. Главная идея - репозиторий-интерфейс, реализацию которого Spring генерирует сам.
Spring Data - не ORM. Он даёт удобный интерфейс поверх ORM. Сама работа с объектами и SQL - у JPA-провайдера, по умолчанию Hibernate.
Spring Data JPA и Hibernate¶
Три слоя, которые часто путают:
- JPA - спецификация: как отображать объекты на таблицы и управлять их состоянием.
Аннотации
@Entity,@Table,@Id,EntityManager - Hibernate - реализация JPA: генерирует SQL, держит persistence context, кэширует (см. Hibernate)
- Spring Data JPA - часть Spring Data поверх JPA: репозитории без реализации, запросы по имени метода, пагинация
spring-boot-starter-data-jpa - стартер, который подключает всё сразу: Spring Data JPA, Hibernate как реализацию
JPA, пул соединений HikariCP. Если Hibernate в classpath, Boot сам настраивает его через
автоконфигурацию: DataSource, EntityManagerFactory, менеджер транзакций.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Настройка подключения¶
spring.datasource.url=jdbc:postgresql://localhost:5432/your_database_name
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.datasource.url- JDBC-адрес базыspring.datasource.username,password- учётные данныеspring.datasource.driver-class-name- класс драйвера. Boot обычно определяет его по URL самspring.jpa.hibernate.ddl-auto- что Hibernate делает со схемой при стартеspring.jpa.show-sql- печатать SQL в консоль, удобно для отладкиspring.jpa.properties.hibernate.format_sql- печатать SQL с отступами
ddl-auto - режимы управления схемой:
| Значение | Что делает |
|---|---|
none |
Ничего |
validate |
Проверяет, что схема совпадает с сущностями, иначе падает при старте |
update |
Добавляет новые таблицы и столбцы. Ничего не удаляет и не переименовывает |
create |
Пересоздаёт схему при старте |
create-drop |
Пересоздаёт при старте и удаляет при остановке. Для тестов |
Warning
update удобен на учебном проекте, но на проде схему ведут миграциями (Flyway, Liquibase), а Hibernate ставят в
validate или none.
Note
В исходной заметке был spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect. В
Hibernate 6 (Spring Boot 3) диалект определяется по подключению сам, и это свойство обычно убирают, иначе Hibernate
пишет предупреждение.
Репозитории¶
Репозиторий - интерфейс доступа к сущности. Реализацию генерирует Spring Data при старте.
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
List<Employee> findByLastName(String lastName); // запрос по имени метода
@Query("select e from Employee e where e.salary > :min") // свой JPQL
List<Employee> findRich(@Param("min") BigDecimal min);
}
JpaRepository<T, ID>даёт CRUD, пагинацию и сортировку:save,findById,findAll,delete- методы по имени (
findByLastName) превращаются в JPQL-запрос @Query- свой JPQL, или нативный SQL сnativeQuery = true- реализация по умолчанию -
SimpleJpaRepository, её методы уже@Transactional
Как проходит вызов employeeRepository.findAll():
- Прокси репозитория передаёт вызов в
SimpleJpaRepository. - Spring Data строит JPQL или Criteria-запрос.
- Hibernate переводит его в SQL под конкретную СУБД и выполняет через JDBC.
- Hibernate превращает строки результата в объекты
Employeeи кладёт их в persistence context.
Слои типового приложения: сущности (@Entity) → репозитории (JpaRepository) → сервисы → контроллеры, которые
принимают HTTP-запросы и зовут сервисы.
Запросы в репозиториях¶
findById и Optional. findById() возвращает Optional<T>: записи может не быть, и вызывающий код обязан это
обработать (см. Запросы в DAO и Optional).
JOIN FETCH в @Query - решает N+1: дети загружаются в том же запросе. Своим методом или переопределив стандартный:
public interface ClientRepository extends JpaRepository<Client, Integer> {
@Query("select c from Client c join fetch c.payments")
List<Client> findAllWithPayments(); // свой метод
@Override
@Query("select c from Client c join fetch c.payments")
List<Client> findAll(); // переопределённый стандартный
}
@EntityGraph - план загрузки прямо на методе репозитория, без JPQL:
@EntityGraph(attributePaths = {"coordinates", "admin"}) // загрузить связи сразу
List<Vehicle> findByType(VehicleType type);
См. N+1 и планы загрузки.
@Modifying - массовый UPDATE или DELETE через @Query. Без неё Spring Data считает запрос выборкой.
@Modifying(clearAutomatically = true) // очистить persistence context после запроса
@Transactional
@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date")
int deactivateInactiveUsers(@Param("date") LocalDate date); // вернёт число изменённых строк
- запрос идёт мимо persistence context: загруженные раньше сущности останутся со старыми значениями.
clearAutomatically = trueочищает контекст после запроса - нужна транзакция:
@Transactionalна методе или вызов из транзакционного сервиса, иначеTransactionRequiredException. Свои методы репозитория транзакцию по умолчанию не получают
@Lock - пессимистичная блокировка на методе репозитория (см. Pessimistic locking).
Аудит сущностей¶
Аудит - Spring Data сам заполняет поля «кто и когда создал или изменил запись».
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware") // включает аудит, указывает бин текущего пользователя
public class JpaConfig {
@Bean
public AuditorAware<Integer> auditorAware() {
return new ApplicationAuditAware(); // берёт id пользователя из SecurityContext
}
}
@Entity
@EntityListeners(AuditingEntityListener.class) // без слушателя поля не заполнятся
public class Token {
@CreatedDate private Instant createdAt;
@LastModifiedDate private Instant updatedAt;
@CreatedBy private Integer createdBy;
@LastModifiedBy private Integer updatedBy;
}
@CreatedDate,@LastModifiedDate- время создания и изменения@CreatedBy,@LastModifiedBy- кто создал и изменил, значение берётся изAuditorAware
TODO¶
- Создание модели данных: сущности, связи, пример
Employee