JPA/Hibernate-Muster
Verwenden für Datenmodellierung, Repositories und Performance-Tuning in Spring Boot.
Wann aktivieren
- Entwerfen von JPA-Entitäten und Tabellen-Mappings
- Definieren von Beziehungen (@OneToMany, @ManyToOne, @ManyToMany)
- Optimieren von Abfragen (N+1-Vermeidung, Fetch-Strategien, Projektionen)
- Konfigurieren von Transaktionen, Auditing oder Soft Deletes
- Einrichten von Paginierung, Sortierung oder benutzerdefinierten Repository-Methoden
- Tuning von Connection Pooling (HikariCP) oder Second-Level-Caching
Entity-Design
@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;
}
Auditing aktivieren:
@Configuration
@EnableJpaAuditing
class JpaConfig {}
Beziehungen und N+1-Vermeidung
@OneToMany(mappedBy = "market", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PositionEntity> positions = new ArrayList<>();
- Standardmäßig Lazy Loading verwenden;
JOIN FETCHin Abfragen einsetzen, wenn nötig EAGERbei Kollektionen vermeiden; für Lese-Pfade DTO-Projektionen verwenden
@Query("select m from MarketEntity m left join fetch m.positions where m.id = :id")
Optional<MarketEntity> findWithPositions(@Param("id") Long id);
Repository-Muster
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
Optional<MarketEntity> findBySlug(String slug);
@Query("select m from MarketEntity m where m.status = :status")
Page<MarketEntity> findByStatus(@Param("status") MarketStatus status, Pageable pageable);
}
- Projektionen für leichte Abfragen verwenden:
public interface MarketSummary {
Long getId();
String getName();
MarketStatus getStatus();
}
Page<MarketSummary> findAllBy(Pageable pageable);
Transaktionen
- Service-Methoden mit
@Transactionalannotieren @Transactional(readOnly = true)für Lese-Pfade verwenden, um zu optimieren- Propagation sorgfältig wählen; langlaufende Transaktionen vermeiden
@Transactional
public Market updateStatus(Long id, MarketStatus status) {
MarketEntity entity = repo.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Market"));
entity.setStatus(status);
return Market.from(entity);
}
Paginierung
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<MarketEntity> markets = repo.findByStatus(MarketStatus.ACTIVE, page);
Für Cursor-ähnliche Paginierung id > :lastId in JPQL mit Sortierung einfügen.
Indizierung und Performance
- Indizes für häufige Filter (
status,slug, Fremdschlüssel) hinzufügen - Zusammengesetzte Indizes passend zu Abfragemustern (
status, created_at) verwenden select *vermeiden; nur benötigte Spalten projizieren- Batch-Schreibvorgänge mit
saveAllundhibernate.jdbc.batch_sizedurchführen
Connection Pooling (HikariCP)
Empfohlene Eigenschaften:
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
Für PostgreSQL LOB-Handling hinzufügen:
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
Caching
- Der First-Level-Cache ist pro EntityManager; Entitäten nicht über Transaktionen hinweg behalten
- Für leseintensive Entitäten Second-Level-Cache mit Vorsicht erwägen; Evict-Strategie validieren
Migrationen
- Flyway oder Liquibase verwenden; in der Produktion nie auf Hibernate Auto DDL verlassen
- Migrationen idempotent und additiv halten; Spalten nicht ohne Plan entfernen
Testen des Datenzugriffs
@DataJpaTestmit Testcontainers bevorzugen, um die Produktion abzubilden- SQL-Effizienz mit Logs überprüfen:
logging.level.org.hibernate.SQL=DEBUGundlogging.level.org.hibernate.orm.jdbc.bind=TRACEfür Parameterwerte setzen
Denken Sie daran: Entitäten schlank halten, Abfragen absichtsvoll durchführen und Transaktionen kurz halten. N+1-Abfragen mit Fetch-Strategien und Projektionen verhindern und für Lese-/Schreib-Pfade indizieren.