Mam przypadek użycia, w którym wywołuje to:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
Zauważ, że @Transactional
ma Propagation.REQUIRES_NEW i repozytorium używa getOne . Po uruchomieniu aplikacji otrzymuję następujący komunikat o błędzie:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
Ale jeśli zmienić getOne(id)
przez findOne(id)
wszystkich utworów grzywny.
BTW, tuż przed wywołaniem metody getUserControlById przez przypadek użycia już wywołał metodę insertUserControl
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
Obie metody to Propagation.REQUIRES_NEW, ponieważ wykonuję prostą kontrolę audytu .
Używam tej getOne
metody, ponieważ jest zdefiniowana w interfejsie JpaRepository , a mój interfejs Repozytorium się stamtąd rozciąga, oczywiście pracuję z JPA.
JpaRepository interfejs rozciąga się od CrudRepository . findOne(id)
Sposób jest zdefiniowany w CrudRepository
.
Moje pytania to:
- Dlaczego zawiodłaś
getOne(id)
metoda? - Kiedy należy stosować tę
getOne(id)
metodę?
Pracuję z innymi repozytoriami i wszystkie używam tej getOne(id)
metody i wszystko działa dobrze, tylko wtedy, gdy używam Propagation.REQUIRES_NEW, nie udaje się.
Zgodnie z getOne API:
Zwraca odwołanie do jednostki o podanym identyfikatorze.
Zgodnie z findOne API:
Pobiera jednostkę na podstawie jej identyfikatora.
3) Kiedy należy stosować findOne(id)
metodę?
4) Jaka metoda jest zalecana?
Z góry dziękuję.
źródło
Odpowiedzi:
TL; DR
T findOne(ID id)
(nazwa w starym API) /Optional<T> findById(ID id)
(nazwa w nowym API) polega naEntityManager.find()
tym, że wykonuje żądne ładowanie jednostki .T getOne(ID id)
polega naEntityManager.getReference()
tym, że wykonuje leniwe ładowanie jednostki . Aby zapewnić efektywne ładowanie jednostki, wymagane jest wywołanie metody.findOne()/findById()
jest naprawdę bardziej przejrzysty i prostszy w użyciu niżgetOne()
.Tak więc w większości przypadków bardzo sprzyjają
findOne()/findById()
ponadgetOne()
.Zmiana interfejsu API
Od przynajmniej
2.0
wersjiSpring-Data-Jpa
zmodyfikowanejfindOne()
.Wcześniej był zdefiniowany w
CrudRepository
interfejsie jako:Teraz jedyną
findOne()
metodą, którą znajdziesz,CrudRepository
jest ta zdefiniowana wQueryByExampleExecutor
interfejsie jako:Jest to ostatecznie realizowane przez
SimpleJpaRepository
domyślną implementacjęCrudRepository
interfejsu.Ta metoda jest zapytaniem przez przykładowe wyszukiwanie i nie chcesz tego jako zamiennika.
W rzeczywistości metoda o tym samym zachowaniu jest nadal dostępna w nowym interfejsie API, ale nazwa metody uległa zmianie.
Jego nazwa została zmieniona od
findOne()
celufindById()
wCrudRepository
interfejsie:Teraz zwraca
Optional
. Którym nie jest tak źle zapobiegaćNullPointerException
.Tak więc rzeczywisty wybór jest teraz między
Optional<T> findById(ID id)
aT getOne(ID id)
.Dwie różne metody, które opierają się na dwóch różnych metodach pobierania JPA EntityManager
1)
Optional<T> findById(ID id)
Javadoc stwierdza, że:Kiedy przyglądamy się implementacji, widzimy, że polega ona na
EntityManager.find()
pobieraniu:I tu
em.find()
jestEntityManager
metoda zadeklarowana jako:Jego javadoc stwierdza:
Zatem pobranie załadowanej jednostki wydaje się oczekiwane.
2) Podczas gdy
T getOne(ID id)
javadoc stwierdza (podkreślenie moje):W rzeczywistości terminologia referencyjna to tak naprawdę płyta, a API JPA nie określa żadnej
getOne()
metody.Dlatego najlepszą rzeczą, jaką można zrobić, aby zrozumieć, co robi opakowanie Spring, jest sprawdzenie implementacji:
Tutaj
em.getReference()
jestEntityManager
metoda zadeklarowana jako:I na szczęście
EntityManager
javadoc lepiej zdefiniował swój zamiar (podkreślenie moje):Zatem wywołanie
getOne()
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,
getOne()
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żesz uzyskać
null
odwołanie jako wynik zserializowany lub jeśli metoda jest wywoływana dla obiektu proxy, zostanie zgłoszony wyjątek, taki jakLazyInitializationException
.Tak więc w tego rodzaju sytuacji wyrzucenie
EntityNotFoundException
tego jest głównym powodem użyciagetOne()
do obsługi wystąpienia, które nie istnieje w bazie danych, ponieważ sytuacja błędu może nigdy nie zostać wykonana, gdy jednostka nie istnieje.W każdym razie, aby zapewnić jego załadowanie, musisz manipulować bytem podczas otwierania sesji. Możesz to zrobić, wywołując dowolną metodę na encji.
Lub lepsze alternatywne zastosowanie
findById(ID id)
zamiast.Dlaczego tak niejasny interfejs API?
Na koniec dwa pytania do programistów Spring-Data-JPA:
dlaczego nie mieć bardziej przejrzystej dokumentacji
getOne()
? Leniwe ładowanie jednostki tak naprawdę nie jest szczegółem.dlaczego musisz wprowadzić
getOne()
do wrapaEM.getReference()
?Dlaczego nie po prostu trzymać się zawiniętego metody:
getReference()
? Ta metoda EM jest naprawdę bardzo szczególna, podczas gdygetOne()
przenosi tak proste przetwarzanie.źródło
getOne()
używa leniwego ładowania i zgłasza,EntityNotFoundException
jeśli żaden element nie zostanie znaleziony.findById()
ładuje się od razu i zwraca wartość null, jeśli nie zostanie znaleziona. Ponieważ w przypadku metody getOne () występują nieprzewidywalne sytuacje, zamiast tego zaleca się użycie funkcji findById ().Podstawowa różnica polega na tym, że
getOne
ładuje się leniwie ifindOne
nie jest.Rozważmy następujący przykład:
źródło
1. Dlaczego metoda getOne (id) zawodzi?
Zobacz tę sekcję w dokumentacji . Zastąpienie już istniejącej transakcji może być przyczyną problemu. Jednak bez dodatkowych informacji trudno odpowiedzieć na to pytanie.
2. Kiedy należy używać metody getOne (id)?
Bez zagłębiania się w wewnętrzne elementy Spring Data JPA wydaje się, że różnica polega na mechanizmie używanym do pobierania jednostki.
Jeśli spojrzysz na JavaDoc w
getOne(ID)
sekcji Zobacz też :wydaje się, że ta metoda po prostu oddelegowuje do realizacji menedżera podmiotu WZP.
Jednak docs dla
findOne(ID)
nie wspomnieć o tym.Wskazówka jest również w nazwach repozytoriów.
JpaRepository
jest specyficzny dla WZP i dlatego może w razie potrzeby delegować połączenia do zarządzającego podmiotem.CrudRepository
jest agnostykiem stosowanej technologii trwałości. Spójrz tutaj . Jest używany jako interfejs znacznika dla wielu technologii trwałości, takich jak JPA, Neo4J itp.Więc tak naprawdę nie ma „różnicy” w tych dwóch metodach dla twoich przypadków użycia, po prostu
findOne(ID)
jest to bardziej ogólne niż bardziej wyspecjalizowanegetOne(ID)
. To, którego użyjesz, zależy od ciebie i twojego projektu, ale osobiście trzymałbym się tego,findOne(ID)
ponieważ sprawia, że twój kod jest mniej specyficzny dla implementacji i otwiera drzwi do przejścia do rzeczy takich jak MongoDB itp. W przyszłości bez zbytniego refaktoryzacji :)źródło
there's not really a 'difference' in the two methods
miejscu jest bardzo mylące , ponieważ naprawdę istnieje duża różnica w sposobie pobierania jednostki i oczekiwaniu, że metoda zwróci. Odpowiedź zamieszczona dalej przez @davidxxx bardzo dobrze to podkreśla i myślę, że każdy, kto używa Spring Data JPA, powinien o tym wiedzieć. W przeciwnym razie może powodować spory ból głowy.Te
getOne
metody powroty odniesienie z DB (leniwa załadunku). Więc w zasadzie jesteś poza transakcją (toTransactional
, że zostałeś zadeklarowany w klasie usług nie jest brane pod uwagę) i występuje błąd.źródło
Powyższe odpowiedzi są dla mnie bardzo trudne. Z punktu widzenia debugowania spędziłem prawie 8 godzin, aby poznać głupi błąd.
Mam testowanie wiosna + hibernacja + spycharka + projekt MySQL. Żeby było jasne.
Mam podmiot użytkownika, podmiot książki. Wykonujesz obliczenia mapowania.
Czy wiele książek było powiązanych z jednym użytkownikiem? Ale w UserServiceImpl próbowałem go znaleźć przez getOne (userId);
Wynik Reszty to
}
Powyższy kod nie pobrał książek, które czytają powiedzmy użytkownicy.
Lista bookList była zawsze pusta z powodu getOne (ID). Po zmianie na findOne (ID). Wynik to
}
źródło
podczas gdy spring.jpa.open-in-view było prawdziwe, nie miałem żadnego problemu z getOne, ale po ustawieniu go na false otrzymałem LazyInitializationException. Następnie problem został rozwiązany poprzez zastąpienie go findById.
Chociaż istnieje inne rozwiązanie bez zastępowania metody getOne, a mianowicie metoda @Transactional, która wywołuje repository.getOne (id). W ten sposób transakcja będzie istniała, a sesja nie zostanie zamknięta w Twojej metodzie, a podczas korzystania z encji nie będzie żadnego LazyInitializationException.
źródło
Miałem podobny problem ze zrozumieniem, dlaczego JpaRespository.getOne (id) nie działa i generuje błąd.
Poszedłem i zmieniłem na JpaRespository.findById (id), który wymaga zwrócenia opcjonalnego.
To chyba mój pierwszy komentarz na temat StackOverflow.
źródło