2013-07-18 13 views
9

Sono abbastanza nuovo per JPA e Hibernate (sto studiando sodo però!) E sto lottando con un problema che non riesco a trovare una soluzione banale per , quindi eccolo qui.Hibernate: inizializzazione pigra contro hashcode rotto/equivale equivale

Ho un'entità che sembra un po 'come il seguente:

@Entity 
@Table(name = "mytable1") 
public class EntityOne { 
    // surrogate key, database generated 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "id") 
    private Long id; 

    // business key 
    @Column(name = "identifier", nullable = false, unique = true) 
    private String identifier; 

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) 
    @JoinColumn(name = "twoId", nullable = false) 
    private EntityTwo two; 

    @OneToMany(mappedBy = "entityOne", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true) 
    private Set<EntityThree> resources = new HashSet<>(); 

    // getters/setters omitted 

    @Override 
    public int hashCode() { 
    // the business key should always be defined (through constructor/query) 
    // if this is null the class violates the general hashcode contract 
    // that the integer value returned must always be the same 
    Assert.notNull(identifier); 
    // a dirty alternative would be: 
    // if(identifier==null) return 0; 
    return identifier.hashCode(); 
    } 

    @Override 
    public boolean equals(Object o) { 
    return o instanceof ResourceGroup 
     && ((ResourceGroup) o).identifier.equals(identifier); 
    } 
} 

Il mio progetto è impostato con Primavera APP, quindi ho il mio CrudRepository<EntityOne,Long> iniettato in una classe di servizio che ha un paio di @Transactional metodi e scruto i miei pacchetti dominio/servizio per JPA e transazioni rispettivamente.

Uno dei metodi di servizio chiama il metodo findAll() del repository e restituisce un elenco di EntityOne s. Tutto funziona bene a meno che io provo ad accedere al getter per two, che getta ovviamente:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session 

ho pensato che potrebbe essere utile avere questo oggetto inizializzato, così ho acceso il tipo recupero da pigro per ansioso. Tuttavia, se faccio che ottengo il seguente:

java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null 
    at org.springframework.util.Assert.notNull(Assert.java:112) 
    at org.springframework.util.Assert.notNull(Assert.java:123) 
    at my.pkg.domain.EntityOne.hashCode(ResourceGroup.java:74) 
    at java.util.HashMap.hash(HashMap.java:351) 
    at java.util.HashMap.put(HashMap.java:471) 
    at java.util.HashSet.add(HashSet.java:217) 
    at java.util.AbstractCollection.addAll(AbstractCollection.java:334) 
    at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:346) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:243) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:233) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:209) 
    at org.hibernate.loader.Loader.endCollectionLoad(Loader.java:1149) 
//... 

ho guardato brevemente il codice sorgente di Hibernate e sembra che si stia cercando di mettere i miei EntityOne oggetti in un insieme prima la loro chiave di business viene inizializzato. La mia interpretazione è corretta? C'è un modo per aggirare questo? Sto facendo qualcosa di incredibilmente stupido?

Apprezzo il vostro aiuto

EDIT: Voglio solo chiarire che quello che sto cercando di capire qui è che cosa le migliori pratiche sono specificamente rispetto al JPA e Hibernate. Se si trattasse di un semplice POJO, potrei rendere definitivo il campo dell'identificatore (in realtà renderei l'intera classe immutabile) ed essere sicuro. Non posso farlo perché sto usando JPA. Quindi le domande: violate il contratto hashCode e in che modo? In che modo Hibernate si occupa di questa violazione? Qual è il modo consigliato da JPA per farlo in generale? Devo eliminare completamente le raccolte basate sugli hash e utilizzare invece gli elenchi?

Giovanni

+0

Ho sostituito tutti i miei valori relazionali 'Set' fields 'con' new LinkedHashMap()' invece di 'new HashMap()' e ha iniziato a funzionare. Non è strano? –

risposta

0

Credo di aver trovato un modo per rendere questo lavoro un po 'migliore, ovvero forzare Hibernate (o qualsiasi altro provider JPA) a rendere disponibile la chiave prima di attaccare gli oggetti nella raccolta. In questo scenario, l'oggetto verrà inizializzato correttamente e possiamo essere sicuri che la chiave aziendale non sarà nulla.

Ad esempio, ecco come la classe EntityTwo avrebbe dovuto cercare:

@Entity 
@Table(name = "mytable2") 
public class EntityTwo { 
    // other code omitted ... 
    @OneToMany(mappedBy = "entityTwo", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true) 
    @MapKey(name = "identifier") 
    private Map<String, EntityOne> entityOnes = new HashMap<>(); 
} 

non ho ancora testato questo codice specifico, ma ho altri esempi di lavoro e dovrebbe funzionare bene secondo il JPA docs. In questo caso, il provider JPA è messo alle strette: deve conoscere il valore di identifier prima di poter inserire l'oggetto nella raccolta. Inoltre, gli oggetti e equals non vengono nemmeno chiamati perché la mappatura è gestita esplicitamente dal fornitore JPA.

Questo è un caso in cui forzare esplicitamente lo strumento a comprendere il modo in cui le cose sono modellate e correlate l'una con l'altra porta a un grande vantaggio.

0

L'interpretazione è corretta. Come primo primo passaggio, codifica il tuo hashCode() e equals() con il tuo campo id - quello che stai dicendo a Hibernate che è il tuo ID.

Come seconda fase implementare un numero corretto hashCode() e equals() per salvarti da problemi futuri. Ci sono molte risorse se lo fai su Google. Here è uno su questo sito

8

No, non stai facendo niente di stupido. L'implementazione di equals e hashCode su un'entità JPA è una questione di molto riscaldamento, debate, e tutti gli approcci che conosco hanno degli svantaggi significativi. Non c'è una soluzione ovvia e banale che ti manca.

Hai, tuttavia, colpito un caso che non è discusso molto per qualche motivo. Il wiki di ibernazione recommends utilizzando una chiave aziendale come si sta facendo e "Persistenza di Java con Hibernate" (Bauer/King, 2007, ampiamente considerato come il lavoro di riferimento di Hibernate standard) a pagina 398 raccomanda la stessa cosa.Ma in alcune situazioni, come si osserva, Hibernate può aggiungere un'entità in un Set prima che i suoi campi siano inizializzati, quindi l'hashCode basato sulla chiave business non funziona, proprio come fai notare. Vedi Hibernate issue HHH-3799 per la discussione di questo caso. C'è un errore previsto test case nel codice sorgente di Hibernate che dimostra il problema, aggiunto nel 2010, quindi almeno uno sviluppatore di Hibernate considera un bug e vuole correggerlo, ma dal 2010 non c'è stata alcuna attività Si prega di prendere in considerazione la votazione per quel problema.

Una soluzione che si potrebbe considerare è di espandere l'ambito della sessione in modo che tutto l'accesso alle entità avvenga all'interno della stessa sessione. Quindi puoi fare in modo che il tuo Set<EntityThree> venga scaricato in modo pigro invece che impazzito, e tu eviterai il problema del recupero di entusiasmo in HHH-3799. La maggior parte delle applicazioni su cui ho lavorato fanno solo un uso parsimonioso degli oggetti in stato di distacco. Sembra che tu stia caricando la tua entità e poi la usi per un po 'dopo la fine della sessione; questo è uno schema che raccomanderei contro. Se stai scrivendo un'applicazione web, vedi il pattern "apri la sessione in vista" e Spring's OpenSessionInViewFilter per idee su come farlo.

Per inciso, mi piace come si genera un'eccezione quando la chiave aziendale non è inizializzata; in questo modo è possibile rilevare rapidamente gli errori di codifica. La nostra applicazione ha un brutto bug a causa di HHH-3799 che avremmo potuto riscontrare nello sviluppo se avessimo usato la tua affermazione non nulla.

+0

Sfortunatamente per farlo funzionare ho dovuto rimuovere l'asserzione. Penso che fallisca anche all'interno della stessa transazione, devo ricontrollare. Grazie per la risposta però! –