it-swarm.com.de

JPA-Eifersuchlauf tritt nicht bei

Was genau steuert die Abrufstrategie von JPA? Ich kann keinen Unterschied zwischen eifrig und faul feststellen. In beiden Fällen verbindet JPA/Hibernate nicht automatisch mehrere Beziehungen.

Beispiel: Person hat eine einzelne Adresse. Eine Adresse kann vielen Personen gehören. Die mit JPA-Annotationen versehenen Entitätsklassen sehen folgendermaßen aus:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

Wenn ich die JPA-Abfrage verwende:

select p from Person p where ...

JPA/Hibernate generiert eine SQL-Abfrage zur Auswahl aus der Personentabelle und anschließend eine eindeutige Adressabfrage für jede Person:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

Dies ist sehr schlecht für große Ergebnismengen. Bei 1000 Personen werden 1001 Abfragen generiert (1 von Person und 1000 von Adresse). Ich weiß das, weil ich das Abfrageprotokoll von MySQL betrachte. Nach meinem Verständnis führt das Festlegen des Abruftyps der Adresse zu einer automatischen Abfrage von JPA/Hibernate bei einem Join. Unabhängig vom Abruftyp werden jedoch weiterhin eindeutige Abfragen für Beziehungen generiert.

Nur wenn ich ihn ausdrücklich auffordere, beizutreten, wird er tatsächlich beitreten:

select p, a from Person p left join p.address a where ...

Vermisse ich hier etwas? Ich muss jetzt jede Abfrage so codieren, dass sie die vielen-zu-eins-Beziehungen miteinander verbindet. Ich verwende die JPA-Implementierung von Hibernate mit MySQL.

Bearbeiten: Es erscheint (siehe Ruhezustand FAQ hier und hier ) dass FetchType keine Auswirkungen auf JPA-Abfragen hat. In meinem Fall habe ich es daher ausdrücklich angewiesen, beizutreten.

103
Steve Kuo

JPA bietet keine Spezifikation für das Zuordnen von Annotationen zur Auswahl der Abrufstrategie. Im Allgemeinen können verwandte Entitäten auf eine der folgenden Arten abgerufen werden

  • SELECT => eine Abfrage für Stammentitäten + eine Abfrage für verwandte zugeordnete Entität/Sammlung jeder Stammentität = (n + 1) Abfragen
  • SUBSELECT => eine Abfrage für Stammentitäten + zweite Abfrage für zugeordnete Entität/Sammlung aller in der ersten Abfrage abgerufenen Stammentitäten = 2 Abfragen
  • JOIN => eine Abfrage zum Abrufen beider Stammentitäten und aller zugeordneten Entitäten/Sammlungen = 1 Abfrage

Also sind SELECT und JOIN zwei Extreme und SUBSELECT liegt dazwischen. Man kann die geeignete Strategie basierend auf seinem Domain-Modell auswählen.

Standardmäßig wird SELECT sowohl von JPA/EclipseLink als auch von Hibernate verwendet. Dies kann überschrieben werden mit:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

im Winterschlaf. Es erlaubt auch, den SELECT -Modus explizit unter Verwendung von @Fetch(FetchMode.SELECT) einzustellen, was unter Verwendung der Stapelgröße, z. @BatchSize(size=10).

Entsprechende Anmerkungen in EclipseLink sind:

@JoinFetch
@BatchFetch
89
Yadu

"mxc" ist richtig. fetchType spezifiziert nur wann die Beziehung sollte aufgelöst werden.

Um das eifrige Laden mithilfe eines Outer Joins zu optimieren, müssen Sie hinzufügen

@Fetch(FetchMode.JOIN)

zu Ihrem Feld. Dies ist eine für den Ruhezustand spezifische Anmerkung.

43
rudolfson

Das Attribut fetchType steuert, ob das mit Annotationen versehene Feld sofort abgerufen wird, wenn die primäre Entität abgerufen wird. Es ist nicht unbedingt vorgeschrieben, wie die Fetch-Anweisung aufgebaut ist. Die tatsächliche SQL-Implementierung hängt von dem Provider ab, den Sie für den Toplink/Ruhezustand verwenden.

Wenn Sie fetchType=EAGER Dies bedeutet, dass das mit Annotationen versehene Feld gleichzeitig mit den Werten der anderen Felder in der Entität gefüllt wird. Wenn Sie also einen Entitätsmanager öffnen, um Ihre Personenobjekte abzurufen und dann den Entitätsmanager zu schließen, führt das anschließende Ausführen einer person.address nicht dazu, dass eine Lazy Load-Ausnahme ausgelöst wird.

Wenn Sie fetchType=LAZY Das Feld wird nur gefüllt, wenn darauf zugegriffen wird. Wenn Sie den Entitymanager bis zu diesem Zeitpunkt geschlossen haben, wird eine Lazy Load-Ausnahme ausgelöst, wenn Sie eine person.address-Anweisung ausführen. Um das Feld zu laden, müssen Sie die Entität mit em.merge () wieder in einen Entitätsmanagerkontext versetzen, dann den Feldzugriff ausführen und dann den Entitätsmanager schließen.

Möglicherweise möchten Sie beim Erstellen einer Kundenklasse mit einer Sammlung für Kundenbestellungen das Laden verzögern. Wenn Sie jede Bestellung für einen Kunden abgerufen haben, als Sie eine Kundenliste erhalten wollten, kann dies eine teure Datenbankoperation sein, wenn Sie nur nach Kundennamen und Kontaktdaten suchen. Am besten lassen Sie den DB-Zugang bis später.

Für den zweiten Teil der Frage: Wie kann der Ruhezustand aktiviert werden, um optimiertes SQL zu generieren?

Im Ruhezustand sollten Sie Hinweise zur Erstellung der effizientesten Abfrage geben können, aber ich vermute, dass mit Ihrer Tabellenkonstruktion etwas nicht stimmt. Ist die Beziehung in den Tabellen festgelegt? Der Ruhezustand hat möglicherweise entschieden, dass eine einfache Abfrage schneller als ein Join ist, insbesondere wenn Indizes usw. fehlen.

36
mxc

Versuche es mit:

select p from Person p left join FETCH p.address a where...

Es funktioniert für mich in einer ähnlichen Version mit JPA2/EclipseLink, aber es scheint, dass diese Funktion auch in JPA1 vorhanden ist :

18
sinuhepop

Wenn Sie EclipseLink anstelle von Ruhezustand verwenden, können Sie Ihre Abfragen durch "Abfragehinweise" optimieren. Lesen Sie diesen Artikel aus dem Eclipse-Wiki: EclipseLink/Examples/JPA/QueryOptimization .

Es gibt ein Kapitel über "Verbundenes Lesen".

um mitzumachen, können Sie mehrere Dinge tun (mit eclipselink)

  • in jpql kannst du left join fetch machen

  • in der benannten Abfrage können Sie einen Abfragehinweis angeben

  • in TypedQuery kann man so etwas sagen

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • es gibt auch einen Batch-Fetch-Hinweis

    query.setHint("eclipselink.batch", "e.address");

sehen

http://Java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html

2
Kalpesh Soni

Ich hatte genau dieses Problem mit der Ausnahme, dass die Person-Klasse eine eingebettete Schlüsselklasse hatte. Meine eigene Lösung bestand darin, sie in die Abfrage AND remove einzubeziehen

@Fetch(FetchMode.JOIN)

Meine eingebettete ID-Klasse:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}
1
Gunslinger