🗄️ Что это
Этот навык (skill) предоставляет набор проверенных паттернов и рекомендаций для работы с JPA и Hibernate в Spring Boot. Он охватывает проектирование сущностей (entities), настройку связей, оптимизацию запросов, управление транзакциями, аудит, индексирование, пагинацию и пул соединений. Skill ориентирован на Java-разработчиков, которые хотят избежать типичных ошибок (N+1, ленивая загрузка, утечки соединений) и строить производительные, поддерживаемые слои доступа к данным.
⚙️ Как работает
🧱 Проектирование сущностей (Entity Design)
Skill рекомендует использовать аннотации JPA для маппинга таблиц, индексов и аудита. Пример базовой сущности:
@Entity
@Table(name = "markets", indexes = {
@Index(name = "idx_markets_slug", columnList = "slug", unique = true)
})
@EntityListeners(AuditingEntityListener.class)
public class MarketEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
@Column(nullable = false, unique = true, length = 120)
private String slug;
@Enumerated(EnumType.STRING)
private MarketStatus status = MarketStatus.ACTIVE;
@CreatedDate
private Instant createdAt;
@LastModifiedDate
private Instant updatedAt;
}
Для включения аудита добавляется конфигурация:
@Configuration
@EnableJpaAuditing
class JpaConfig {}
🔗 Отношения и предотвращение N+1
Связи (@OneToMany, @ManyToOne, @ManyToMany) по умолчанию используют ленивую загрузку (lazy loading). Чтобы избежать проблемы N+1 запросов, skill предлагает:
- Использовать
JOIN FETCH в JPQL-запросах для явной загрузки связанных коллекций.
- Для read-операций применять DTO-проекции (интерфейсы с геттерами), чтобы не загружать целые сущности.
- Избегать
EAGER на коллекциях.
Пример запроса с JOIN FETCH:
@Query("select m from MarketEntity m left join fetch m.positions where m.id = :id")
Optional`MarketEntity` findWithPositions(@Param("id") Long id);
📂 Репозитории и проекции
Репозитории наследуются от JpaRepository. Skill рекомендует:
- Использовать именованные методы (например,
findBySlug).
- Для лёгких запросов создавать проекции — интерфейсы с геттерами только нужных полей.
- Применять
Pageable для пагинации.
Пример проекции:
public interface MarketSummary {
Long getId();
String getName();
MarketStatus getStatus();
}
Page`MarketSummary` findAllBy(Pageable pageable);
🔄 Транзакции
- Сервисные методы помечаются
@Transactional.
- Для read-only операций используется
@Transactional(readOnly = true) — это оптимизирует работу Hibernate (отключает dirty checking).
- Важно выбирать правильный propagation и избегать длинных транзакций.
Пример:
@Transactional
public Market updateStatus(Long id, MarketStatus status) {
MarketEntity entity = repo.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Market"));
entity.setStatus(status);
return Market.from(entity);
}
📄 Пагинация и сортировка
Skill показывает, как создавать PageRequest с сортировкой:
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page`MarketEntity` markets = repo.findByStatus(MarketStatus.ACTIVE, page);
Для курсороподобной пагинации (keyset pagination) предлагается добавлять условие id > :lastId в JPQL.
🏷️ Индексирование и производительность
- Индексы создаются на часто фильтруемые поля (
status, slug, внешние ключи).
- Составные индексы должны соответствовать шаблонам запросов (например,
status, created_at).
- Избегать
SELECT * — проекции только нужных столбцов.
- Для массовой вставки использовать
saveAll и настройку hibernate.jdbc.batch_size.
🔌 Connection Pooling (HikariCP)
Рекомендуемые параметры в application.properties:
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.validation-timeout=5000
Для PostgreSQL с LOB-полями добавляется:
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
🧠 Кэширование
- Первый уровень кэша (1st-level cache) работает в рамках
EntityManager; не стоит держать сущности между транзакциями.
- Второй уровень (2nd-level cache) рассматривается только для read-heavy сущностей, с обязательной стратегией вытеснения (eviction).
🛠️ Миграции
- Использовать Flyway или Liquibase, никогда не полагаться на
spring.jpa.hibernate.ddl-auto в production.
- Миграции должны быть идемпотентными и аддитивными; удаление колонок — только после тщательного планирования.
🧪 Тестирование доступа к данным
- Для интеграционных тестов применять
@DataJpaTest с Testcontainers, чтобы окружение было близко к production.
- Включать SQL-логи для проверки эффективности запросов:
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
🎯 Когда использовать
- При проектировании новых JPA-сущностей и таблиц.
- При настройке связей между сущностями (один-ко-многим, многие-к-одному, многие-ко-многим).
- Для оптимизации запросов: устранение N+1, выбор fetch-стратегий, использование проекций.
- При конфигурировании транзакций, аудита (created/updated timestamps) или soft delete.
- Для реализации пагинации, сортировки и кастомных методов репозитория.
- При настройке пула соединений HikariCP или второго уровня кэша.
⚠️ Важно знать
- Сущности должны быть простыми, запросы — осознанными, транзакции — короткими.
- N+1 — главный враг производительности. Всегда проверяйте сгенерированные SQL-запросы.
- Не используйте EAGER на коллекциях — это может привести к загрузке всей базы.
- Проекции (DTO) легче и быстрее, чем загрузка целых сущностей, особенно для read-операций.
- Индексы — дешёвый способ ускорить запросы, но не забывайте про составные индексы под конкретные фильтры.
- Миграции должны быть под контролем — Flyway/Liquibase обязательны в production.
- Тестируйте с реальной БД (Testcontainers), чтобы отловить проблемы с диалектом или типами.
Комментарии
Комментариев пока нет. Будьте первым.