4 Stimmen

Vermeidung von n+1-Selects mit zwischengespeicherten Hibernate-Assoziationen oder Caching-Sammlungen als Ganzes

Ich habe eine Eins-zu-Viel-Beziehung: Übergeordneter Datensatz mit n untergeordneten Datensätzen. Diese Datensätze werden häufig verwendet und sind schreibgeschützt und eignen sich gut für die Zwischenspeicherung.

Hier ist eine Annäherung an meine Hibernate-Zuordnung:

`<class name="Parent" table="Parent>
   <cache usage="read-only"/>
   <id name="primary_key"/>
   <property name="natural_key"/>

   <set name="children" lazy="false" fetch="join">
      <cache usage="read-only"/>
      <key-column name="parent_id"/>
      <one-to-many class="Child"/>
   </set>
</class>

<class name="Child" table="Child">
   <cache usage="read-only"/>
   <id name="primary_key"/>
   <property name="parent_id"/>
</class>`

Ich rufe das Elternteil häufig über einen natürlichen Schlüssel und nicht über einen Primärschlüssel ab, so dass ich Query Caching aktivieren muss, um den 2nd Level Cache zu nutzen (ich verwende ehcache).

Hier ist das Problem: Wenn ich ein Elternteil abrufe und einen Treffer im Abfrage-Cache erhalte, wird daraus eine "Abruf nach Primärschlüssel"-Abfrage. Dies ist gut für das "eine" Ende meiner One-to-Many-Abfrage. Wenn das Elternteil nicht im Cache gefunden wird, wird es aus der DB abgerufen. Wenn meine n Child-Datensätze nicht im Cache gefunden werden, holt Hibernate sie mit n nachfolgenden Select-Abfragen. N+1 select Problem.

Was ich will ist eine Möglichkeit, die Sammlung von Child-Objekten, die durch die parent_id verschlüsselt sind, zwischenzuspeichern. Ich möchte, dass Hibernate im Cache nach meiner Sammlung als Ganzes sucht und nicht nur nach einzelnen Datensätzen. Wenn die Sammlung nicht gefunden wird, möchte ich, dass Hibernate die Sammlung mit einer Select-Anweisung abruft - alle Childs mit parent_id=x abrufen.

Ist das zu viel verlangt von Hibernate + ehcache?

5voto

bsevans Punkte 91

Ich habe meine eigene Antwort gefunden - es ist möglich, Hibernate + ehcache so zu konfigurieren, dass es das tut, was ich oben beschrieben habe.

Indem ich mein Child als Value-Type und nicht als Entity-Type deklariere (ich glaube, das sind die Begriffe, die die Hibernate-Gemeinschaft verwendet), kann ich mein Child im Wesentlichen als eine Komponente von Parent und nicht als separate Entität behandeln. Hier ist ein Beispiel für mein überarbeitetes Mapping:

<class name="Parent" table="Parent">
   <cache usage="read-only"/>
   <id name="primary_key"/>
   <property name="natural_key"/>

   <set name="children" lazy="false" fetch="join" table="Child">
      <cache usage="read-only"/>
      <key-column name="parent_id"/>
      <composite-element class="Child">
         <property name="property1" column="PROP1" type="string">
         <property name="property2" column="PROP2" type="string">
      </composite-element>
   </set>
</class>

Das Verhalten meiner Child-Objekte ist unter dieser Konfiguration etwas anders als zuvor - es ist kein separater Primärschlüssel für Child definiert, keine gemeinsamen Referenzen und keine löschbaren Felder/Spalten. Siehe die Hibernate-Dokumente für weitere Einzelheiten zu diesem Thema.

Meine Parent und Child sind beide schreibgeschützt, und ich möchte eigentlich nur über eine Parent auf Instanzen von Child zugreifen - ich verwende Child nicht unabhängig von Parent, daher ist die wertartige Behandlung für meinen Anwendungsfall gut geeignet.

Der große Gewinn für mich war, dass die Sammlung in meiner neuen Konfiguration zwischengespeichert wird. Der Sammlungscache speichert jetzt meine Sammlung als Ganzes, verschlüsselt nach parent_id. Es ist nicht mehr möglich, dass sich einige, aber nicht alle Elemente meiner Sammlung im Cache befinden. Die Sammlung wird als Ganzes im Cache gespeichert und entfernt. Noch wichtiger ist, dass Hibernate, wenn es im 2nd Level Cache nach meiner Sammlung sucht und einen Fehler findet, die gesamte Sammlung mit einer einzigen Select-Abfrage aus der DB abruft.

Hier ist meine ehcache-Konfiguration:

 <ehcache>
    <cache name="query.Parent"
        maxElementsInMemory="10"
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="43200" 
    </cache>
    <cache name="Parent"
        maxElementsInMemory="10"
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="43200" 
    </cache>
    <cache name="Parent.children"
        maxElementsInMemory="10"
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="43200" 
    </cache>
<ehcache>

Ich hoffe, dieses Beispiel hilft jemand anderem.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X