JPA eifriger Abruf tritt nicht bei

112

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 viele-zu-eins-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 eigene 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 mir das Abfrageprotokoll von MySQL ansehe. Nach meinem Verständnis führt das Festlegen des Abruftyps der Adresse auf eifrig dazu, dass JPA / Hibernate automatisch mit einem Join abfragt. Unabhängig vom Abruftyp werden jedoch immer noch unterschiedliche Abfragen für Beziehungen generiert.

Nur wenn ich ihm ausdrücklich sage, dass er beitreten soll, tritt er tatsächlich bei:

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

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

Bearbeiten: Es scheint (siehe FAQ zum Ruhezustand hier und hier ), dass FetchTypeJPA-Abfragen keine Auswirkungen haben. In meinem Fall habe ich ihm ausdrücklich gesagt, dass er beitreten soll.

Steve Kuo
quelle
5
Links zu FAQ-Einträgen sind defekt, hier funktioniert ein
n0weak

Antworten:

99

JPA bietet keine Spezifikation für die Zuordnung von Anmerkungen 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äten / Sammlungen jeder Stammentität = (n + 1) Abfragen
  • SUBSELECT => eine Abfrage für Stammentitäten + zweite Abfrage für verwandte zugeordnete Entitäten / Sammlung aller Stammentitäten, die in der ersten Abfrage abgerufen wurden = 2 Abfragen
  • JOIN => eine Abfrage zum Abrufen beider Stammentitäten und aller zugeordneten Entitäten / Sammlung = 1 Abfrage

Also SELECTund JOINsind zwei Extreme und SUBSELECTfällt dazwischen. Man kann eine geeignete Strategie basierend auf seinem Domain-Modell wählen.

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

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

im Ruhezustand. Es ermöglicht auch die SELECTexplizite Einstellung des Modus, @Fetch(FetchMode.SELECT)der mithilfe der Stapelgröße eingestellt werden kann, z @BatchSize(size=10).

Entsprechende Anmerkungen in EclipseLink sind:

@JoinFetch
@BatchFetch
Yadu
quelle
5
Warum existieren diese Einstellungen? Ich denke, dass JOIN fast immer verwendet werden muss. Jetzt muss ich alle Zuordnungen mit Ruhezustandsspezifischen Anmerkungen markieren.
Vbezhenar
4
Interessant, aber leider funktioniert @Fetch (FetchMode.JOIN) bei mir (Hibernate 4.2.15) in JPQL wie in Criteria überhaupt nicht.
Aphax
3
Die Hibernate-Annotation scheint auch für mich überhaupt nicht zu funktionieren, wenn Spring JPA
TheBakker
2
@Aphax Dies kann daran liegen, dass Hibernate andere Standardstrategien für JPAQL / Criteria verwendet als em.find (). Siehe vladmihalcea.com/2013/10/17/… und die Referenzdokumentation.
Joshua Davis
1
@vbezhenar (und andere lesen seinen Kommentar einige Zeit später): JOIN-Abfrage generiert kartesisches Produkt in der Datenbank, sodass Sie sicher sein sollten, dass dieses kartesische Produkt berechnet werden soll. Beachten Sie, dass wenn Sie den Abruf-Join verwenden, auch wenn Sie LAZY setzen, dieser eifrig geladen wird.
Walfrat
45

"mxc" ist richtig. fetchTypeGibt nur an, wann die Beziehung aufgelöst werden soll.

Um das eifrige Laden mithilfe eines äußeren Joins zu optimieren, müssen Sie hinzufügen

@Fetch(FetchMode.JOIN)

zu Ihrem Feld. Dies ist eine Annotation im Ruhezustand.

Rudolfson
quelle
6
Dies funktioniert bei Hibernate 4.2.15 in JPQL oder Criteria nicht.
Aphax
6
@Aphax Ich denke, das liegt daran, dass JPAQL und Kriterien die Fetch-Spezifikation nicht einhalten. Die Fetch-Annotation funktioniert nur für em.find (), AFAIK. Siehe vladmihalcea.com/2013/10/17/… Siehe auch die Dokumente zum Ruhezustand. Ich bin mir ziemlich sicher, dass dies irgendwo abgedeckt ist.
Joshua Davis
@JoshuaDavis Was ich damit meine ist, dass die Annotation \ @Fetch keine JOIN-Optimierung in den Abfragen anwendet, egal ob JPQL oder em.find (). Ich habe gerade einen weiteren Versuch mit Hibernate 5.2. + Durchgeführt und es ist immer noch dasselbe
Aphax
38

Das Attribut fetchType steuert, ob das mit Anmerkungen versehene Feld sofort abgerufen wird, wenn die primäre Entität abgerufen wird. Es gibt nicht unbedingt vor, wie die Abrufanweisung aufgebaut ist. Die tatsächliche SQL-Implementierung hängt von dem Anbieter ab, den Sie für den Link / Ruhezustand usw. verwenden.

Wenn Sie festlegen fetchType=EAGERDies bedeutet, dass das mit Anmerkungen versehene Feld gleichzeitig mit den anderen Feldern in der Entität mit seinen Werten gefüllt wird. Wenn Sie also einen Entitymanager öffnen, rufen Sie Ihre Personenobjekte ab und schließen Sie den Entitymanager. Wenn Sie anschließend eine person.address ausführen, wird keine Ausnahme für das verzögerte Laden ausgelöst.

Wenn Sie festlegen, wird fetchType=LAZYdas Feld nur beim Zugriff ausgefüllt. Wenn Sie den Entitymanager bis dahin geschlossen haben, wird eine verzögerte Ladeausnahme ausgelöst, wenn Sie eine person.address ausführen. Um das Feld zu laden, müssen Sie die Entität mit em.merge () wieder in einen EntityManger-Kontext stellen, dann den Feldzugriff durchführen und dann den Entitymanager schließen.

Möglicherweise möchten Sie beim Erstellen einer Kundenklasse mit einer Sammlung für Kundenaufträge verzögert laden. 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 Datenbankzugang bis später.

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

Im Ruhezustand sollten Sie Hinweise zum Erstellen 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.

mxc
quelle
20

Versuche es mit:

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

Es funktioniert bei mir ähnlich wie bei JPA2 / EclipseLink, aber es scheint, dass diese Funktion auch in JPA1 vorhanden ist :

Sinuhepop
quelle
7

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

Es gibt ein Kapitel über "Joined Reading".

uı6ʎɹnɯ ꞁəıuɐp
quelle
2

Um Mitglied zu werden, können Sie mehrere Dinge tun (mit eclipselink)

  • in jpql können sie left join abrufen

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

  • In TypedQuery können Sie so etwas sagen

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

  • Es gibt auch einen Batch-Abruf-Hinweis

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

sehen

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

Kalpesh Soni
quelle
1

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 einzubeziehen UND zu entfernen

@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();
    }

}
Revolverheld
quelle
-1

Mir fallen zwei Dinge ein.

Sind Sie sicher, dass Sie ManyToOne als Adresse meinen? Das bedeutet, dass mehrere Personen dieselbe Adresse haben. Wenn es für einen von ihnen bearbeitet wird, wird es für alle bearbeitet. Ist das deine Absicht? 99% der Zeitadressen sind "privat" (in dem Sinne, dass sie nur einer Person gehören).

Zweitens, haben Sie andere eifrige Beziehungen zur Entität Person? Wenn ich mich richtig erinnere, kann Hibernate nur eine eifrige Beziehung zu einer Entität verarbeiten, aber das sind möglicherweise veraltete Informationen.

Ich sage das, weil Ihr Verständnis, wie dies funktionieren sollte, von meinem Sitzplatz aus im Wesentlichen richtig ist.

Cletus
quelle
Dies ist ein erfundenes Beispiel, das viele zu eins verwendet. Personenadresse war möglicherweise nicht das beste Beispiel. Ich sehe keine anderen eifrigen Abruftypen in meinem Code.
Steve Kuo
Mein Vorschlag ist dann, dies auf ein einfaches Beispiel zu reduzieren, das ausgeführt wird und das tut, was Sie sehen, und das dann zu posten. Es kann andere Komplikationen in Ihrem Modell geben, die unerwartetes Verhalten verursachen.
Cletus
1
Ich habe den Code genau so ausgeführt, wie er oben angezeigt wird, und er weist das besagte Verhalten auf.
Steve Kuo