2013-07-25 12 views
17

Ho un problema con le relazioni di EAGER in una grande applicazione. Alcune entità in questa applicazione hanno associazioni EAGER con altre entità. Questo diventa "veleno" in alcune funzionalità.Ignora un FetchType.EAGER in una relazione

Ora il mio team ha bisogno di ottimizzare queste funzionalità, ma non possiamo cambiare il tipo di recupero in LAZY, perché avremmo bisogno di rifattorizzare l'intera applicazione.

Quindi, la mia domanda: c'è un modo per fare una query specifica ignorando le associazioni di EAGER nella mia entità restituita?

Esempio: quando ho questa persona Persona, vorrei non portare l'elenco di indirizzi quando faccio una query per trovare una persona.

@Entity 
public class Person { 

    @Column 
    private String name; 

    @OneToMany(fetch=FetchType.EAGER) 
    private List<String> address; 

} 

Query query = EntityManager.createQuery("FROM Person person"); 
//list of person without the address list! But how??? 
List<Person> resultList = query.getResultList(); 

Grazie!

Aggiornato

L'unico modo che ho trovato non restituisce l'entità, tornando solo alcuni campi dell'entità. Ma mi piacerebbe trovare una soluzione che possa restituire all'entità (nel mio esempio, l'entità Person).

Sto pensando se è possibile mappare due volte la stessa tabella in Hibernate. In questo modo, posso mappare la stessa tabella senza le associazioni EAGER. Questo mi aiuterà in alcuni casi ...

+0

http://stackoverflow.com/questions/10997321/how-to-override-fetchtype-eager-to-be-lazy-at-runtime?lq=1 –

+1

Forse stai meglio refactoring. In genere, EAGER verrebbe utilizzato solo per relazioni banali, se così fosse. Meglio avere tutto a LAZY e poi ottenere l'extra quando sai che ne hai bisogno. –

+0

Ciao @Nicholas! È la soluzione migliore Purtroppo, non è possibile refactoring tutte le entità perché c'è un sacco di impatto nell'applicazione. – Dherik

risposta

3

mai effettivamente provato questo, ma potrebbe pena di un colpo ... Partendo dal presupposto che la fabbrica di sessione è disponibile presso lo strato DAO tramite iniezione o qualsiasi altro mezzo è possibile implementare qualcosa di simile in un (probabilmente nuovo) metodo DAO:

List<Person> result = (List<Person>) sessionFactory.getCurrentSession() 
     .createCriteria(Person.class) 
     .setFetchMode("address", FetchMode.LAZY) 
     .list(); 
return result; 
+0

Ciao! Buona risposta. Ho fatto più ricerche e penso che il tuo suggerimento sia l'unico modo ... Un amico mi consiglia di vedere FetchProfile: http://www.concretepage.com/hibernate/fetchprofile_hibernate_annotation.php – Dherik

+0

Purtroppo, FetchProfile non funziona con Hibernate 3.3 .0 (la versione di Hibernate della mia applicazione). Non sono in grado di verificare se FetchProfile può risolvere il problema anche in Hibernate 3.5.0 (prima versione con FetchProfile) – Dherik

+3

Come per i documenti di ibernazione, 'FetchMode.LAZY' è deprecato ed è equivalente a' FetchMode.SELECT'. Quindi 'FetchMode.LAZY' risulta in effetti nell'attivazione di una query SELECT aggiuntiva per caricare avidamente la raccolta. Questo in realtà non carica LAZYily raccolta. –

1
  1. Sì, è possibile mappare due classi di entità allo stesso tavolo e che è una soluzione valida. Tuttavia, fai attenzione alle situazioni in cui le istanze di entrambi i tipi sono presenti contemporaneamente nello stesso contesto di persistenza, poiché gli aggiornamenti di un'istanza di entità di un tipo non si riflettono sulla stessa istanza dell'altro tipo. Inoltre, il caching di secondo livello di tali entità diventa più complicato.
  2. Fetch profiles sono anche interessanti, ma al momento sono molto limitati ed è possibile sovrascrivere il piano/la strategia di recupero predefiniti solo con i profili di recupero stile join (è possibile associare un'associazione pigra, ma non viceversa). Tuttavia, è possibile utilizzare questo trick per invertire tale comportamento: Rendere l'associazione pigra per impostazione predefinita e abilitare il profilo per impostazione predefinita per tutte le sessioni/transazioni. Quindi disabilitare il profilo nelle transazioni in cui si desidera il caricamento lento.
1

Non hai detto perché non si poteva passare da desiderosi a pigri. Tuttavia, ho avuto l'impressione che fosse per motivi di prestazioni e quindi voglio mettere in discussione questa ipotesi. In tal caso, considera quanto segue. Mi rendo conto che questa risposta non risponda rigorosamente alla tua domanda e viola le condizioni di nessun carico pigro, ma qui c'è un'alternativa che riflette l'approccio del mio team di sviluppo a quello che penso sia lo stesso problema di fondo.

Impostare il tipo di recupero su pigro, quindi impostare un'annotazione @BatchSize. Poiché in genere l'ibernazione utilizza una query DB separata per caricare una raccolta, questo mantiene tale comportamento ma con un BatchSize ottimizzato, si evita 1 query per elemento (ad esempio in un ciclo), a condizione che la sessione sia ancora aperta.

Il comportamento per il backref di una relazione OneToOne diventa un po 'divertente (il lato di riferimento della relazione - il lato senza la chiave esterna). Ma per l'altro lato di OneToOne, per OneToMany e ManyToOne, questo dà il risultato finale che penso tu vorresti: basta interrogare le tabelle se effettivamente ne hai bisogno ma eviti un carico pigro per record, e non lo fai configurare esplicitamente ciascun caso d'uso. Ciò significa che le tue prestazioni rimarranno paragonabili nel caso in cui tu esegua il carico pigro, ma questo carico non si verifica se non ne hai effettivamente bisogno.

+0

è per la perfomance! Abbiamo un sacco di entità con EAGER. La tua soluzione non risponde direttamente ma è un'ottima alternativa per risolvere il problema delle prestazioni in alcuni casi in cui EAGER è ovunque, come nel mio caso. – Dherik

2

Per impostazione predefinita, HQL, Criteria e NativeSQL di Hibernate ci offrono la flessibilità di caricare EAGERly una raccolta se è mappata come LAZY nel modello di dominio.

Per quanto riguarda il contrario, vale a dire, mappare la raccolta come EAGER nel modello di dominio e provare a eseguire il caricamento LAZY utilizzando HQL, Criteria o NativeSQL, non sono riuscito a trovare un modo semplice o più semplice in cui può soddisfarlo con HQL/Criteria/NativeSQL.

Anche se abbiamo FetchMode.LAZY che possiamo impostare su Criteri, è deprecato ed equivale a FetchMode.SELECT. E in effetti, FetchMode.LAZY risulta effettivamente nell'attivazione di una query SELECT aggiuntiva e carica ancora impazientemente la raccolta.

Tuttavia, se si desidera caricare LAZY una raccolta mappata come EAGER, è possibile provare questa soluzione: rendere HQL/Criteria/NativeSQL per restituire i valori scalari e utilizzare un valore ResultTransformer(Transformers.aliasToBean(..)) per restituire l'oggetto entità (o DTO) con campi popolati da valori scalari.

Nel mio scenario, ho una Foresta entità che ha una collezione di Albero entità OneToMany mappatura delle FetchType.EAGER e FetchMode.JOIN. Per caricare solo l'entità Foresta senza caricare alcun albero, ho utilizzato la seguente query HQL con valori scalari e Transformers.aliasToBean (...). Funziona con Criteria e Native SQL e purché si utilizzino scalari e aliasToBean Transformer.

Forest forest = (Forest) session.createQuery("select f.id as id, f.name as name, f.version as version from Forest as f where f.id=:forest").setParameter("forest", i).setResultTransformer(Transformers.aliasToBean(Forest.class)).uniqueResult();

Ho testato per sopra query semplice e potrebbe essere di lavoro controllando se questo funziona per casi complessi come bene e si adatta a tutte le vostre casi d'uso.

Sarebbe desideroso di sapere se esiste un modo migliore o più semplice di farlo in particolare senza scalari e trasformatori.

+0

Molto bello. La soluzione è come restituire un nuovo 'VO':' seleziona new MyVO (f.id, f.name) FROM [...] ', ma usando l'entità. Ho bisogno di verificare se questo approccio posso usare l'entità restituita (nel tuo esempio, Forest) per fare tutto ciò che posso fare con un'entità "normale", come usarla per creare un'associazione con un'altra entità. Come: 'treeNormalEntity.setForest (forestFromTransformer);'. – Dherik

7

Se si utilizza JPA 2.1 (Hibernate 4.3+) è possibile ottenere ciò che si desidera con @NamedEntityGraph.

In sostanza, si dovrebbe annotare il soggetto in questo modo:

@Entity 
@NamedEntityGraph(name = "Persons.noAddress") 
public class Person { 

    @Column 
    private String name; 

    @OneToMany(fetch=FetchType.EAGER) 
    private List<String> address; 

} 

e quindi utilizzare i suggerimenti per andare a prendere Privato senza indirizzo, in questo modo:

EntityGraph graph = this.em.getEntityGraph("Persons.noAddress"); 

Map hints = new HashMap(); 
hints.put("javax.persistence.fetchgraph", graph); 

return this.em.findAll(Person.class, hints); 

più sul tema si possono trovare here .

Quando si utilizza il grafico di recupero, solo i campi che sono stati inseriti in @NamedEntityGraph verranno recuperati con entusiasmo.

Tutte le query esistenti che vengono eseguite senza il suggerimento rimarranno le stesse.

+0

Molto buono. Dico in un altro commento la mia versione di Hibernate (3.3.0), ma tu rispondi aiuterà le persone con lo stesso problema usando le versioni più recenti. Grazie! – Dherik

+1

In pratica questo non funziona, Hibernate continuerà a recuperare gli attributi contrassegnati come EAGER anche se non li includi nel tuo EntityGraph, vedi: https://hibernate.atlassian.net/browse/HHH-8776 Questa risposta non può sono stati testati perché non funziona come previsto. –

+0

Provato questo approccio, non funziona. –

Problemi correlati