2013-07-18 11 views
9

Ich bin ziemlich neu zu JPA und Hibernate (ich studiere hart aber!) Und ich kämpfe mit einem Problem, für das ich nicht scheinen kann, eine triviale Lösung zu finden , hier ist es also.Hibernate: Lazy Initialisierung vs gebrochenen Hashcode/Gleichgestellte Rätsel

Ich habe eine Entität, die ein bisschen wie folgt aussieht:

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

Mein Projekt ist mit Frühling JPA eingerichtet, so habe ich meine CrudRepository<EntityOne,Long> in einer Service-Klasse injiziert, die ein paar @Transactional Methoden hat und ich scannen meine Domain/Service-Pakete für JPA bzw. Transaktionen.

Eine der Servicemethoden ruft die Methode findAll() des Repositorys auf und gibt eine Liste mit EntityOne s zurück. Alles funktioniert gut, wenn ich versuche, den Getter für den Zugriff auf für two, die offensichtlich wirft:

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

Ich dachte, es nützlich sein könnte, dieses Objekt initialisiert zu haben, so schaltete ich die Abruf Art von faul zu eifrig. Allerdings, wenn ich, dass ich folgendes erhalten:

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) 
//... 

ich kurz sah Hibernate Quellcode und es sieht aus wie es versucht, meine EntityOne Objekte in einem Satz vor ihr Geschäft Schlüssel initialisiert zu setzen. Ist meine Interpretation richtig? Gibt es einen Weg dahin? Mache ich etwas unglaublich dummes?

Ich schätze Ihre Hilfe

EDIT: Ich will nur, dass zu klären, was ich versuche, hier zu verstehen ist, was die besten Praktiken speziell in Bezug auf PPV und Hibernate. Wenn dies ein einfaches POJO wäre, könnte ich das Identifier-Feld final machen (ich würde die ganze Klasse tatsächlich unveränderlich machen) und sicher sein. Ich kann das nicht, weil ich JPA verwende. Also die Fragen: verletzen Sie den hashCode-Vertrag und auf welche Weise? Wie geht Hibernate mit dieser Verletzung um? Was ist der von der JPA empfohlene Weg, dies generell zu tun? Soll ich Hash-basierte Sammlungen komplett loswerden und stattdessen Listen verwenden?

Giovanni

+0

Ich ersetzte alle meine ursprünglichen 'Set'-Felder mit' new LinkedHashMap() 'anstelle von' new HashMap() 'und es begann zu arbeiten. Ist es nicht seltsam? –

Antwort

0

Ich glaube, ich habe tatsächlich einen Weg gefunden, diese Arbeit ein wenig besser zu machen, d. H. Hibernate (oder welcher JPA-Anbieter) den Schlüssel verfügbar zu haben, bevor Objekte in der Sammlung kleben. In diesem Szenario wird das Objekt ordnungsgemäß initialisiert und wir können sicher sein, dass der Geschäftsschlüssel nicht null ist.

Zum Beispiel, hier ist, wie die Klasse EntityTwo würde suchen müssen:

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

ich diesen spezifischen Code nicht getestet, aber ich habe andere Arbeitsbeispiele und es soll die JPA docs nach gut funktionieren. In diesem Fall ist der JPA-Anbieter in die Enge getrieben: Er muss den Wert identifier kennen, bevor er das Objekt in die Sammlung setzen kann. Außerdem werden die Objekte hashCode und equals nicht einmal aufgerufen, da das Mapping explizit vom JPA-Provider gehandhabt wird.

Dies ist ein Fall, in dem das explizite Erzwingen des Werkzeugs, die Art und Weise zu verstehen, wie die Dinge modelliert und miteinander verwandt werden, zu einem großen Nutzen führt.

0

Ihre Interpretation ist korrekt. Als ersten ersten Schritt code Ihre hashCode() und equals() mit Ihrem id Feld - die, die Sie Hibernate erzählen, die Ihre ID ist.

Als zweiten Schritt implementieren Sie eine korrekte hashCode() und equals(), um Sie vor zukünftigen Problemen zu retten. Es gibt viele Ressourcen, wenn Sie es googlen. Here ist einer auf dieser Seite

8

Nein, du machst nichts Dummes. Das Implementieren von equals und hashCode auf einer JPA-Entität ist eine Angelegenheit von viel erhitztem debate, und alle Ansätze, die ich kenne, haben signifikante Nachteile. Es gibt keine offensichtliche, triviale Lösung, die Sie gerade vermissen.

Sie haben jedoch einen Fall getroffen, der aus irgendeinem Grund nicht viel diskutiert wird. Das Hibernate-Wiki recommends mit einem Business-Key, wie Sie es tun, und "Java Persistence mit Hibernate" (Bauer/King, 2007, weithin als das Standard-Hibernate-Referenzwerk betrachtet) auf Seite 398 empfiehlt das gleiche.In einigen Situationen kann Hibernate jedoch eine Entität zu einem Set hinzufügen, bevor die Felder initialisiert werden. Daher funktioniert der business-key-basierte Hash-Code nicht, genau wie Sie darauf hingewiesen haben. Siehe Hibernate-Problem HHH-3799 für die Diskussion dieses Falls. Im Hibernate-Quellcode ist ein Fehler von test case zu erwarten, der das 2010 hinzugefügte Problem aufzeigt, sodass mindestens ein Hibernate-Entwickler es als Fehler ansieht und beheben möchte, aber seit 2010 keine Aktivitäten mehr durchgeführt hat Bitte erwägen Sie, für dieses Thema zu stimmen.

Eine Lösung, die Sie in Erwägung ziehen könnten, besteht darin, den Bereich Ihrer Sitzung zu erweitern, sodass alle Zugriffe auf Entitäten innerhalb derselben Sitzung erfolgen. Dann können Sie Ihre Set<EntityThree> lazy-geholt anstelle von eifrig-abgerufen werden, und Sie werden das Problem mit dem Eager-Holen in HHH-3799 vermeiden. Die meisten Anwendungen, an denen ich gearbeitet habe, verwenden nur Objekte im gelöschten Zustand. Es hört sich so an, als würdest du deine Entity laden und sie nach Beendigung der Sitzung für eine Weile verwenden. Das ist ein Muster, das ich empfehlen würde. Wenn Sie eine Webanwendung schreiben, finden Sie unter dem Muster "Sitzung in Sicht öffnen" und Spring OpenSessionInViewFilter Ideen dazu.

Übrigens mag ich, wie Sie eine Ausnahme auslösen, wenn der Business Key nicht initialisiert ist; Auf diese Weise können Sie Codierungsfehler schnell erkennen. Unsere Anwendung hat einen hässlichen Fehler aufgrund von HHH-3799, den wir möglicherweise in Entwicklung begriffen hätten, wenn wir Ihre Nicht-Null-Behauptung verwendet hätten.

+0

Leider musste ich die Behauptung entfernen, um es zum Laufen zu bringen. Ich denke, es scheitert sogar innerhalb der gleichen Transaktion, ich muss überprüfen. Danke für die Antwort! –