2013-07-18 44 views
9

Jestem całkiem nowy w JPA i Hibernate (ja się intensywnie uczę!) I zmagam się z problemem, który nie wydaje mi się znaleźć trywialne rozwiązanie dla , więc oto jest.Hibernate: Lazy inicjalizacja vs złamany hashcode/równa się zagadka

Mam podmiot, który wygląda trochę jak następuje:

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

Mój projekt jest skonfigurowany z wiosennej WZP, więc mam swoje CrudRepository<EntityOne,Long> wstrzykiwany w klasie usług, który ma kilka @Transactional metod i skanować moje pakiety domen/usług odpowiednio dla JPA i transakcji.

Jedna z metod serwisowych wywołuje metodę repozytorium findAll() i zwraca listę EntityOne s. Wszystko działa poprawnie, chyba że próby uzyskania dostępu do getter dla two, co oczywiście rzuca:

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

Pomyślałem, że może to być przydatne, że obiekt ten zainicjowany, więc przeszedłem do pobierania typ od leniwy chętny. Jednakże, jeśli to zrobić uzyskać następujące:

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

krótko spojrzał na kodzie źródłowym Hibernate i wygląda na to, że próbuje umieścić moje EntityOne obiektów w zestawie przed ich kluczowy obiekt jest inicjowany. Czy moja interpretacja jest poprawna? Czy istnieje sposób obejścia tego? Czy robię coś niesamowicie głupiego?

Doceniam twoją pomoc

EDIT: Chcę tylko wyjaśnić, że to, co usiłuję zrozumieć, oto co najlepsze praktyki są specjalnie względem JPA i Hibernate. Gdyby to było zwykłe POJO, mogłem uczynić pole identyfikatora ostatecznym (uczyniłbym całą klasę niezmienną) i byłoby bezpieczne. Nie mogę tego zrobić, ponieważ używam JPA. Tak więc pytania: czy naruszasz umowę hashCode i w jaki sposób? W jaki sposób Hibernate radzi sobie z tym naruszeniem? Jaki jest ogólnie zalecany sposób WZP? Czy powinienem całkowicie pozbyć się kolekcji hashowych i zamiast tego użyć list?

Giovanni

+0

Wymieniłem wszystkie moje relacyjne wartości początkowe 'Set' fields 'z' new LinkedHashMap()' zamiast 'new HashMap()' i zaczęło działać. Czy to nie dziwne? –

Odpowiedz

0

Wydaje mi się, że znalazłem sposób, aby uczynić tę pracę nieco lepszą, tj. Zmuszając Hibernate (lub innego dostawcę JPA) do posiadania klucza przed umieszczeniem obiektów w kolekcji. W tym scenariuszu obiekt zostanie poprawnie zainicjowany i możemy być pewni, że klucz biznesowy nie będzie pusty.

Na przykład, oto jak klasa EntityTwo musiałby wyglądać:

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

Nie testowałem tego konkretnego kodu, ale mam inne przykłady robocze i powinno działać zgodnie z JPA docs. W tym przypadku dostawca JPA jest zapętlony: musi znać wartość identifier, zanim będzie mógł umieścić obiekt w kolekcji. Poza tym obiekty hashCode i equals nie są nawet wywoływane, ponieważ mapowanie jest jawnie obsługiwane przez dostawcę JPA.

Jest to przypadek, w którym wyraźne wymuszenie na narzędziu zrozumienia sposobu modelowania i wzajemnego powiązania ze sobą przynosi ogromne korzyści.

0

Twoja interpretacja jest poprawna. Jako pierwszy kod pierwszego kroku wpisz swoje hashCode() i equals() ze swoim polem - tym, któremu mówisz Hibernate, który jest twoim identyfikatorem.

W drugim kroku należy wprowadzić poprawne hashCode() i equals(), aby uchronić Cię przed przyszłymi problemami. Jest wiele zasobów, jeśli google to. Here jest jednym na tej stronie

8

Nie, nie robisz nic głupiego. Implementacja equals i hashCode w jednostce JPA jest kwestią dużo gorącej debate, a wszystkie podejścia, o których wiem, mają poważne wady. Nie ma oczywistego, trywialnego rozwiązania, którego po prostu brakuje.

Udało ci się jednak trafić w sprawę, która z jakiegoś powodu nie jest zbytnio dyskutowana. Witryna hibernacji recommends używająca klucza biznesowego, podobnie jak "Java Persistence with Hibernate" (Bauer/King, 2007, powszechnie uważana za standardową pracę referencyjną Hibernate) na stronie 398 zaleca to samo.Ale w niektórych sytuacjach, jak zauważysz, Hibernacja może dodać obiekt do zbioru, zanim jego pola zostaną zainicjowane, więc kod mieszania oparty na kluczu biznesowym nie zadziała, tak jak zauważyłeś. Aby omówić tę sprawę, zobacz temat Hibernacja numer HHH-3799. W kodzie źródłowym stanu hibernacji występuje problem z numerem test case, który wskazuje na problem, dodany w 2010 r., Więc co najmniej jeden programista Hibernate uważa, że ​​jest to błąd i chce go naprawić, ale od 2010 r. Nie było żadnej aktywności Proszę rozważyć głosowanie na tę kwestię.

Jednym z rozwiązań, które można rozważyć, jest rozszerzenie zakresu sesji, tak aby cały dostęp do podmiotów odbywał się w ramach tej samej sesji. Wtedy możesz sprawić, że twoje leniwy pobrane zostanie zamiast lęku-pobrane, a unikniesz gorączkowego problemu w HHH-3799. Większość aplikacji, nad którymi pracowałem, czyni tylko oszczędne korzystanie z obiektów w stanie odłączonym. Wygląda na to, że ładujesz swój obiekt, a następnie używasz go przez jakiś czas po zakończeniu sesji; to jest wzór, który polecam. Jeśli piszesz aplikację internetową, zobacz wzorzec "otwarta sesja w widoku" i Spring's OpenSessionInViewFilter, aby dowiedzieć się, jak to zrobić.

Nawiasem mówiąc, podoba mi się sposób zgłaszania wyjątku, gdy klucz biznesowy nie został zainicjowany; w ten sposób możesz szybko łapać błędy kodowania. Nasza aplikacja ma nieprzyjemny błąd z powodu HHH-3799, który moglibyśmy zauważyć, gdybyśmy użyli Twojego niezerowego asercji.

+0

Niestety, aby działało, musiałem usunąć twierdzenie. Myślę, że nie uda się nawet w ramach tej samej transakcji, muszę dwukrotnie sprawdzić. Dzięki za odpowiedź! –