Natknąłem się na sytuację (która moim zdaniem jest dziwna, ale prawdopodobnie jest całkiem normalna), w której używam EntityManager.getReference (LObj.getClass (), LObj.getId ()), aby uzyskać jednostkę bazy danych, a następnie przekazać zwrócony obiekt do być utrwalone w innej tabeli.
Zasadniczo przepływ był taki:
class TFacade { createT (FObj, AObj) { T TObj = nowy T (); TObj.setF (FObj); TObj.setA (AObj); ... EntityManager.persist (TObj); ... L LObj = A.getL (); FObj.setL (LObj); FFacade.editF (FObj); } } @ TransactionAttributeType.REQUIRES_NEW class FFacade { editF (FObj) { L LObj = FObj.getL (); LObj = EntityManager.getReference (LObj.getClass (), LObj.getId ()); ... EntityManager.merge (FObj); ... FLHFacade.create (FObj, LObj); } } @ TransactionAttributeType.REQUIRED class FLHFacade { createFLH (FObj, LObj) { FLH FLHObj = nowy FLH (); FLHObj.setF (FObj); FLHObj.setL (LObj); .... EntityManager.persist (FLHObj); ... } }
Otrzymuję następujący wyjątek „java.lang.IllegalArgumentException: Nieznany podmiot: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0”
Po dłuższym przyjrzeniu się temu w końcu zorientowałem się, że to dlatego, że korzystałem z metody EntityManager.getReference (), otrzymałem powyższy wyjątek, ponieważ metoda zwracała proxy.
To sprawia, że zastanawiam się, kiedy zaleca się użycie metody EntityManager.getReference () zamiast metody EntityManager.find () ?
EntityManager.getReference () zgłasza EntityNotFoundException, jeśli nie może znaleźć poszukiwanej jednostki, co samo w sobie jest bardzo wygodne. Metoda EntityManager.find () zwraca tylko wartość null, jeśli nie może znaleźć jednostki.
Jeśli chodzi o granice transakcji, wydaje mi się, że przed przekazaniem nowo znalezionej jednostki do nowej transakcji trzeba użyć metody find (). Jeśli użyjesz metody getReference (), prawdopodobnie znajdziesz się w sytuacji podobnej do mojej z powyższym wyjątkiem.
źródło
Odpowiedzi:
Zwykle używam metody getReference , gdy nie potrzebuję dostępu do stanu bazy danych (mam na myśli metodę getter). Wystarczy zmienić stan (mam na myśli metodę ustawiającą). Jak powinieneś wiedzieć, getReference zwraca obiekt proxy, który używa potężnej funkcji zwanej automatycznym sprawdzaniem brudnym. Załóżmy, co następuje
Jeśli wywołam metodę znajdowania, zadzwoni dostawca JPA w tle
Jeśli wywołam metodę getReference , dostawca JPA w tle zadzwoni
I wiesz dlaczego ???
Gdy wywołasz getReference, otrzymasz obiekt proxy. Coś takiego (dostawca JPA zajmuje się implementacją tego proxy)
Tak więc przed zatwierdzeniem transakcji dostawca JPA zobaczy flagę stateChanged w celu zaktualizowania podmiotu osoby lub NIE. Jeśli żadne wiersze nie zostaną zaktualizowane po instrukcji aktualizacji, dostawca JPA zgłosi EntityNotFoundException zgodnie ze specyfikacją JPA.
pozdrowienia,
źródło
SELECT
przedUPDATE
, bez względu na to, która zfind()
/getReference()
używam. Co gorsza,SELECT
przemierza relacje NON-LAZY (wydawanie nowychSELECTS
), chociaż chcę tylko zaktualizować pojedyncze pole w jednym encji.Jak wyjaśniłem w tym artykule , zakładając, że masz
Post
podmiot nadrzędny i dziecko,PostComment
jak pokazano na poniższym diagramie:Jeśli dzwonisz
find
, próbując ustawić@ManyToOne
post
skojarzenie:Hibernate wykona następujące instrukcje:
Zapytanie SELECT jest tym razem bezużyteczne, ponieważ nie potrzebujemy do pobrania jednostki Post. Chcemy tylko ustawić bazową kolumnę klucza obcego post_id.
Teraz, jeśli
getReference
zamiast tego użyjesz :Tym razem Hibernate wyda tylko instrukcję INSERT:
W przeciwieństwie do tego
find
,getReference
jedyny zwraca encję Proxy, która ma tylko ustawiony identyfikator. Jeśli uzyskasz dostęp do serwera proxy, skojarzona instrukcja SQL zostanie wyzwolona, dopóki EntityManager będzie nadal otwarty.Jednak w tym przypadku nie musimy uzyskiwać dostępu do jednostki Proxy. Chcemy tylko propagować klucz obcy do bazowego rekordu tabeli, więc załadowanie serwera proxy jest wystarczające w tym przypadku użycia.
Podczas ładowania serwera proxy należy mieć świadomość, że wyjątek LazyInitializationException może zostać zgłoszony, jeśli spróbujesz uzyskać dostęp do odwołania do serwera proxy po zamknięciu obiektu EntityManager. Aby uzyskać więcej informacji na temat obsługi
LazyInitializationException
, zapoznaj się z tym artykułem .źródło
hibernate.jpa.compliance.proxy
właściwość konfiguracyjną , dzięki czemu możesz wybrać zgodność JPA lub lepszą wydajność dostępu do danych.getReference
w ogóle jest potrzebne, jeśli wystarczy ustawić nową instancję modelu z zestawem PK. czego mi brakuje?Ponieważ referencja jest „zarządzana”, ale nie jest nawodniona, może również pozwolić na usunięcie jednostki według identyfikatora, bez konieczności uprzedniego ładowania jej do pamięci.
Ponieważ nie można usunąć niezarządzanej jednostki, po prostu głupio jest ładować wszystkie pola za pomocą funkcji find (...) lub createQuery (...), tylko po to, aby ją natychmiast usunąć.
źródło
EntityManager.getReference()
jest metodą podatną na błędy i jest bardzo niewiele przypadków, w których kod klienta musi jej użyć.Osobiście nigdy nie musiałem go używać.
EntityManager.getReference () i EntityManager.find (): brak różnicy w zakresie kosztów ogólnych
Nie zgadzam się z zaakceptowaną odpowiedzią, aw szczególności:
To nie jest zachowanie, które otrzymuję w Hibernate 5, a javadoc
getReference()
nie mówi czegoś takiego:EntityManager.getReference()
oszczędza kwerendę w celu pobrania jednostki w dwóch przypadkach:1) jeśli jednostka jest przechowywana w kontekście trwałości, jest to pamięć podręczna pierwszego poziomu.
I to zachowanie nie jest specyficzne dla
EntityManager.getReference()
,EntityManager.find()
oszczędza również kwerendę w celu pobrania jednostki, jeśli jednostka jest przechowywana w kontekście trwałości.Możesz sprawdzić pierwszy punkt na dowolnym przykładzie.
Możesz również polegać na rzeczywistej implementacji Hibernacji.
Rzeczywiście,
EntityManager.getReference()
polega nacreateProxyIfNecessary()
metodzieorg.hibernate.event.internal.DefaultLoadEventListener
klasy, aby załadować jednostkę.Oto jego realizacja:
Ciekawą częścią jest:
2) Jeśli nie będziemy efektywnie manipulować bytem, powielając leniwie pobrane javadoc.
Rzeczywiście, aby zapewnić efektywne ładowanie jednostki, wymagane jest wywołanie metody.
Czyli zysk byłby związany ze scenariuszem, w którym chcemy załadować jednostkę bez potrzeby jej używania? W ramach aplikacji taka potrzeba jest naprawdę rzadka, a ponadto
getReference()
zachowanie jest również bardzo mylące, jeśli przeczytasz następną część.Dlaczego warto faworyzować EntityManager.find () zamiast EntityManager.getReference ()
Pod względem kosztów ogólnych
getReference()
nie jest lepszy niżfind()
omówiony w poprzednim punkcie.Więc po co używać jednego lub drugiego?
Wywołanie
getReference()
może zwrócić leniwie pobraną jednostkę.Tutaj leniwe pobieranie nie odnosi się do relacji jednostki, ale do samej jednostki.
Oznacza to, że jeśli wywołamy,
getReference()
a następnie kontekst Persistence zostanie zamknięty, jednostka może nigdy nie zostać załadowana, a więc wynik jest naprawdę nieprzewidywalny. Na przykład, jeśli obiekt proxy jest serializowany, można uzyskaćnull
odwołanie jako wynik zserializowany lub jeśli metoda jest wywoływana dla obiektu proxy, zostanie zgłoszony wyjątek, taki jakLazyInitializationException
.Oznacza to, że wyrzucenie
EntityNotFoundException
tego jest głównym powodem dogetReference()
obsługi instancji, która nie istnieje w bazie danych, ponieważ sytuacja błędu może nigdy nie zostać wykonana, gdy jednostka nie istnieje.EntityManager.find()
nie ma ambicji rzucania,EntityNotFoundException
jeśli istota nie zostanie znaleziona. Jego zachowanie jest zarówno proste, jak i jasne. Nigdy nie będziesz zaskoczony, ponieważ zawsze zwraca załadowaną jednostkę lubnull
(jeśli jednostka nie zostanie znaleziona), ale nigdy nie jest to jednostka w postaci proxy, która może nie zostać skutecznie załadowana.Dlatego
EntityManager.find()
w większości przypadków powinno to być preferowane.źródło
Nie zgadzam się z wybraną odpowiedzią i jak słusznie zauważył davidxxx, getReference nie zapewnia takiego zachowania dynamicznych aktualizacji bez select. Zadałem pytanie dotyczące poprawności tej odpowiedzi, patrz tutaj - nie można zaktualizować bez wydania polecenia select przy użyciu settera po getReference () hibernacji JPA .
Szczerze mówiąc, nie widziałem nikogo, kto faktycznie korzystał z tej funkcji. GDZIEKOLWIEK. I nie rozumiem, dlaczego tak się cieszy.
Po pierwsze, bez względu na to, co wywołasz obiekt proxy z hibernacją, setter lub getter, uruchamiany jest kod SQL i ładowany jest obiekt.
Ale potem pomyślałem, co z tego, jeśli proxy getReference () JPA nie zapewnia takiej funkcjonalności. Mogę po prostu napisać własne proxy.
Teraz wszyscy możemy argumentować, że selekcje kluczy głównych są tak szybkie, jak może uzyskać zapytanie, i tak naprawdę nie jest to coś, czego należy unikać. Ale dla tych z nas, którzy nie mogą sobie z tym poradzić z jakiegoś powodu, poniżej znajduje się implementacja takiego proxy. Ale zanim zobaczysz implementację, zobacz, jak jest używana i jak prosta jest w użyciu.
STOSOWANIE
Spowoduje to uruchomienie następującego zapytania -
a nawet jeśli chcesz wstawić, nadal możesz zrobić PersistenceService.save (new Order ("a", 2)); i wystrzeliłby wkładkę tak, jak powinna.
REALIZACJA
Dodaj to do swojego pom.xml -
Utwórz tę klasę, aby utworzyć dynamiczne proxy -
Utwórz interfejs ze wszystkimi metodami -
Teraz utwórz przechwytywacz, który pozwoli ci zaimplementować te metody na twoim proxy -
I klasa wyjątkowa -
Usługa do zapisywania przy użyciu tego serwera proxy -
źródło