La relazione OneToOne con chiave primaria condivisa genera n + 1 selezioni; qualche soluzione?

Immagina 2 tabelle in un database relazionale, ad esempio Persona e Fatturazione. Esiste un’associazione (non obbligatoria) OneToOne definita tra queste quadro e condivide la chiave primaria di Person (ovvero PERSON_ID è definito in Persona e Fatturazione, ed è una chiave esterna in quest’ultimo).

Quando si effettua una selezione su Persona tramite una query denominata come:

from Person p where p.id = :id 

Hibernate / JPA genera due query selezionate, una sulla tabella Persona e un’altra sulla tabella Fatturazione.

L’esempio sopra è molto semplice e non causerebbe alcun problema di prestazioni, dato che la query restituisce solo un risultato. Ora, immagina che la Person abbia n relazioni OneToOne (tutte non obbligatorie) con altre quadro (tutte condividono la chiave primaria della Person ).

Correggetemi se ho torto, ma l’esecuzione di una query di select su Persona, restituendo r righe, comporterebbe la selezione di (n+1)*r da Hibernate, anche se le associazioni sono pigre .

Esiste una soluzione alternativa per questo potenziale disastro delle prestazioni (oltre a non utilizzare affatto una chiave primaria condivisa)? Grazie per tutte le tue idee.

Immagina 2 tabelle in un database relazionale, ad esempio Persona e Fatturazione. Esiste un’associazione (non obbligatoria) OneToOne definita tra queste entity framework,

Il recupero pigro non è concettualmente ansible per OneToOne non obbligatorio, Hibernate deve colpire il database per sapere se l’associazione è null o no. Maggiori dettagli da questa vecchia pagina wiki:

Alcune spiegazioni sul caricamento lento (one-to-one)

[…]

Ora considera che la nostra class B ha un’associazione one-to-one con C

 class B { private C cee; public C getCee() { return cee; } public void setCee(C cee) { this.cee = cee; } } class C { // Not important really } 

Subito dopo aver caricato B, puoi chiamare getCee() per ottenere C. Ma guarda, getCee() è un metodo della TUA class e Hibernate non ha alcun controllo su di esso. Hibernate non sa quando qualcuno chiamerà getCee() . Ciò significa che Hibernate deve inserire un valore appropriato nella proprietà ” cee ” nel momento in cui carica B dal database. Se il proxy è abilitato per C , Hibernate può mettere un object C-proxy che non è ancora stato caricato, ma verrà caricato quando qualcuno lo usa. Questo dà un carico pigro per l’ one-to-one .

Ma ora immagina che il tuo object B possa o meno avere associato C ( constrained="false" ). Cosa dovrebbe getCee() quando B specifica non ha C ? Nullo. Ma ricorda, Hibernate deve impostare il valore corretto di “cee” nel momento in cui ha impostato B (perché non sa quando qualcuno chiamerà getCee() ). Il proxy non aiuta qui perché il proxy stesso in object già non nullo.

Quindi il curriculum: se la mapping B-> C è obbligatoria ( constrained=true ), Hibernate utilizzerà il proxy per C con conseguente inizializzazione pigra. Ma se si concede B senza C, Hibernate deve solo controllare la presenza di C nel momento in cui carica B. Ma un SELEZIONA per controllare la presenza è solo inefficiente perché la stessa SELEZIONE non può semplicemente controllare la presenza, ma caricare l’intero object. Quindi il carico pigro va via .

Quindi, non è ansible … per impostazione predefinita.

Esiste una soluzione alternativa per questo potenziale disastro delle prestazioni (oltre a non utilizzare affatto una chiave primaria condivisa)? Grazie per tutte le tue idee.

Il problema non è la chiave primaria condivisa, con o senza chiave primaria condivisa, la riceverai, il problema è il nullable OneToOne.

Prima opzione : utilizzare la strumentazione bytecode (consultare i riferimenti alla documentazione di seguito) e il recupero da proxy :

 @OneToOne( fetch = FetchType.LAZY ) @org.hibernate.annotations.LazyToOne(org.hibernate.annotations.LazyToOneOption.NO_PROXY) 

Seconda opzione : usa un falso ManyToOne(fetch=FetchType.LAZY) . Questa è probabilmente la soluzione più semplice (e, a mia conoscenza, quella consigliata). Ma non ho provato questo con un PK condiviso però.

Terza opzione : Eager carica la fatturazione utilizzando un join fetch .

Domanda correlata

  • Rendere pigro un rapporto OneToOne

Riferimenti

  • Guida di riferimento di ibernazione
    • 19.1.3. Proxy di associazione single-ended
    • 19.1.7. Utilizzo del recupero di proprietà lazy
  • Old Hibernate FAQ
    • Come posso impostare una relazione 1 a 1 come pigra?
  • Hibernate Wiki
    • Alcune spiegazioni sul caricamento lento (one-to-one)

Questo è un problema di prestazioni comuni con Hibernate (cerca solo “Hibernate n + 1”). Ci sono tre opzioni per evitare le query n + 1:

  • Dimensione del lotto
  • subselect
  • Fai un SINISTRA SINISTRO nella tua domanda

Questi sono coperti nelle FAQ di Hibernate qui e qui

Potresti provare a “blind-guess optimization”, che è buono per “n + 1 select problems”. Annota il tuo campo (o getter) in questo modo:

 @org.hibernate.annotations.BatchSize(size = 10) java.util.Set bills = new HashSet(); 

Stai lontano dalla mapping OneToOne di Hibernate

È molto rotto e pericoloso. Sei un piccolo bug lontano da un problema di corruzione del database.

http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128

Questo problema “n + 1” si verifica solo se si specifica la relazione come pigro o si indica esplicitamente che si desidera che la ibernazione esegua una query separata.

Hibernate può recuperare la relazione con Fatturazione con un join esterno sulla selezione di Person, eliminando del tutto il problema n + 1. Penso che sia l’indicazione fetch = “XXX” nei tuoi file hbm.

Dai un’occhiata a un breve suggerimento sulle strategie di recupero