JPA Hibernate Header

JPA & Hibernate: 5 Performance-Fehler, die fast jedes Projekt macht

JPA ist nicht langsam — falscher Einsatz schon

Hibernate und JPA haben einen schlechten Ruf in Sachen Performance. Aber in den meisten Fällen liegt das Problem nicht beim Framework, sondern bei typischen Anwendungsfehlern, die sich leicht vermeiden lassen.

Hier sind die fünf häufigsten Performance-Killer — und ihre Lösung.

1. Das N+1 Query Problem

Der Klassiker. Sie laden eine Liste von Bestellungen, und für jede Bestellung wird eine separate Query für den Kunden ausgeführt.

Das Problem:

// 1 Query für Bestellungen + N Queries für Kunden
List<Bestellung> bestellungen = em.createQuery(
    "SELECT b FROM Bestellung b", Bestellung.class)
    .getResultList();

for (Bestellung b : bestellungen) { System.out.println(b.getKunde().getName()); // Lazy Load! }

Die Lösung: JOIN FETCH

List<Bestellung> bestellungen = em.createQuery(
    "SELECT b FROM Bestellung b JOIN FETCH b.kunde",
    Bestellung.class)
    .getResultList();

Eine Query statt N+1. Bei 1.000 Bestellungen ist das der Unterschied zwischen 1 und 1.001 Datenbankaufrufen.

2. Eager Loading als Default

@ManyToOne ist standardmäßig EAGER — das bedeutet, jede Relation wird sofort mitgeladen, auch wenn Sie die Daten gar nicht brauchen.

Die Lösung:

@ManyToOne(fetch = FetchType.LAZY)
private Kunde kunde;

Faustregel: Setzen Sie alles auf LAZY und laden Sie gezielt per JOIN FETCH oder EntityGraph, wenn Sie die Daten brauchen.

3. Fehlende Projektionen

Sie brauchen nur Name und E-Mail, laden aber die komplette Entity mit 30 Feldern, LOBs und Relationen.

Das Problem:

// Lädt ALLES — auch Felder, die Sie nicht brauchen
List<Kunde> kunden = em.createQuery(
    "SELECT k FROM Kunde k", Kunde.class)
    .getResultList();

Die Lösung: DTO-Projektionen

record KundeDto(String name, String email) {}

List<KundeDto> kunden = em.createQuery( "SELECT new at.cgs.dto.KundeDto(k.name, k.email) FROM Kunde k", KundeDto.class) .getResultList();

Weniger Daten über die Leitung, kein Dirty Checking, kein Managed State — deutlich schneller.

4. Batch-Operationen ohne Batching

1.000 Entities einzeln persistieren erzeugt 1.000 INSERT-Statements.

Die Lösung:

# application.properties (Quarkus/Spring)
hibernate.jdbc.batch_size=25
hibernate.order_inserts=true
hibernate.order_updates=true
for (int i = 0; i < entities.size(); i++) {
    em.persist(entities.get(i));
    if (i % 25 == 0) {
        em.flush();
        em.clear(); // First-Level Cache leeren!
    }
}

5. Kein Query-Monitoring

Der gefährlichste Fehler: Sie wissen gar nicht, welche Queries Hibernate generiert.

Aktivieren Sie SQL-Logging in der Entwicklung:

# Quarkus
quarkus.hibernate-orm.log.sql=true

# Spring Boot spring.jpa.show-sql=true logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql=TRACE

Für Produktion: Nutzen Sie Hibernate Statistics oder ein APM-Tool (z.B. OpenTelemetry) um langsame Queries zu identifizieren.

Bonus: EntityGraph statt JOIN FETCH

Für flexible Ladingstrategien ohne Query-Anpassung:

@NamedEntityGraph(
    name = "Bestellung.mitKunde",
    attributeNodes = @NamedAttributeNode("kunde")
)
@Entity
public class Bestellung { ... }

// Verwendung EntityGraph<?> graph = em.getEntityGraph("Bestellung.mitKunde"); List<Bestellung> result = em.createQuery("SELECT b FROM Bestellung b") .setHint("jakarta.persistence.fetchgraph", graph) .getResultList();

Fazit

JPA und Hibernate sind mächtige Werkzeuge — aber sie verzeihen Unwissenheit nicht. Die gute Nachricht: Die fünf häufigsten Fehler sind leicht zu erkennen und zu beheben. Aktivieren Sie SQL-Logging, prüfen Sie Ihre Fetch-Strategien, und nutzen Sie Projektionen wo möglich.

In unseren Training-Angeboten behandeln wir JPA-Performance als festen Bestandteil der Java-Entwickler-Ausbildung.

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert