Ich habe einen Anwendungsfall, in dem Folgendes aufgerufen wird:
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
return this.userControlRepository.getOne(id);
}
Beachten Sie die @Transactional
hat Propagation.REQUIRES_NEW und die Repository - Anwendungen getOne . Wenn ich die App starte, erhalte ich die folgende Fehlermeldung:
Exception in thread "main" org.hibernate.LazyInitializationException:
could not initialize proxy - no Session
...
Aber wenn ich das ändern , getOne(id)
indem findOne(id)
feine alles funktioniert.
Übrigens, kurz bevor der Anwendungsfall die Methode getUserControlById aufruft , hat er bereits die Methode insertUserControl aufgerufen
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
return this.userControlRepository.save(userControl);
}
Beide Methoden sind Propagation.REQUIRES_NEW weil ich eine einfache mache Audit Kontrolle.
Ich benutze die getOne
Methode, weil sie in der JpaRepository- Schnittstelle definiert ist und sich meine Repository-Schnittstelle von dort aus erstreckt. Ich arbeite natürlich mit JPA.
Die JpaRepository- Schnittstelle erstreckt sich von CrudRepository . Die findOne(id)
Methode ist definiert in CrudRepository
.
Meine Fragen sind:
- Warum die
getOne(id)
Methode nicht bestehen? - Wann sollte ich die
getOne(id)
Methode anwenden?
Ich arbeite mit anderen Repositorys und alle verwenden die getOne(id)
Methode und alle funktionieren einwandfrei, nur wenn ich Propagation.REQUIRES_NEW verwende , schlägt dies fehl.
Laut der getOne- API:
Gibt einen Verweis auf die Entität mit dem angegebenen Bezeichner zurück.
Laut der findOne- API:
Ruft eine Entität anhand ihrer ID ab.
3) Wann sollte ich die findOne(id)
Methode anwenden?
4) Welche Methode wird empfohlen?
Danke im Voraus.
quelle
Antworten:
TL; DR
T findOne(ID id)
(Name in der alten API) /Optional<T> findById(ID id)
(Name in der neuen API) basiert darauf,EntityManager.find()
dass eine Entität eifrig geladen wird .T getOne(ID id)
verlässt sich darauf,EntityManager.getReference()
dass eine Entität faul geladen wird . Um das effektive Laden der Entität sicherzustellen, muss eine Methode aufgerufen werden.findOne()/findById()
ist wirklich klarer und einfacher zu bedienen alsgetOne()
.Also in den meisten Fällen lieber
findOne()/findById()
alsgetOne()
.API-Änderung
Zumindest von der
2.0
VersionSpring-Data-Jpa
geändertfindOne()
.Zuvor wurde es in der
CrudRepository
Schnittstelle wie folgt definiert :Die einzige
findOne()
Methode, die Sie finden,CrudRepository
ist die in derQueryByExampleExecutor
Benutzeroberfläche definierte :Dies wird schließlich durch
SimpleJpaRepository
die Standardimplementierung derCrudRepository
Schnittstelle implementiert .Diese Methode ist eine Abfrage anhand einer Beispielsuche, die Sie nicht als Ersatz verwenden möchten.
Tatsächlich ist die Methode mit demselben Verhalten in der neuen API immer noch vorhanden, aber der Methodenname hat sich geändert.
Es wurde in der Benutzeroberfläche von
findOne()
bis umbenannt :findById()
CrudRepository
Jetzt wird ein zurückgegeben
Optional
. Welches ist nicht so schlimm zu verhindernNullPointerException
.Die eigentliche Wahl liegt nun zwischen
Optional<T> findById(ID id)
undT getOne(ID id)
.Zwei unterschiedliche Methoden, die auf zwei unterschiedlichen JPA EntityManager-Abrufmethoden beruhen
1) Der
Optional<T> findById(ID id)
Javadoc erklärt, dass es:Wenn wir uns die Implementierung ansehen, können wir sehen, dass sie
EntityManager.find()
für den Abruf erforderlich ist:Und hier
em.find()
ist eineEntityManager
Methode deklariert als:Sein Javadoc sagt:
Das Abrufen einer geladenen Entität scheint also zu erwarten.
2) Während der
T getOne(ID id)
Javadoc sagt (Schwerpunkt liegt bei mir):Tatsächlich ist die Referenzterminologie wirklich Board und die JPA-API gibt keine
getOne()
Methode an.Um zu verstehen, was der Spring-Wrapper tut, sollten Sie sich am besten mit der Implementierung befassen:
Hier
em.getReference()
ist eineEntityManager
Methode deklariert als:Und zum Glück hat der
EntityManager
Javadoc seine Absicht besser definiert (Schwerpunkt liegt bei mir):Das Aufrufen
getOne()
kann also eine träge abgerufene Entität zurückgeben.Hier bezieht sich das verzögerte Abrufen nicht auf Beziehungen der Entität, sondern auf die Entität selbst.
getOne()
Dies bedeutet, dass wenn wir aufrufen und dann der Persistenzkontext geschlossen wird, die Entität möglicherweise nie geladen wird und das Ergebnis daher wirklich unvorhersehbar ist.Wenn das Proxy-Objekt beispielsweise serialisiert ist, können Sie eine
null
Referenz als serialisiertes Ergebnis erhalten, oder wenn eine Methode für das Proxy-Objekt aufgerufen wird, wird eine AusnahmeLazyInitializationException
ausgelöst.In solchen Situationen ist der Wurf der
EntityNotFoundException
Hauptgrund für diegetOne()
Behandlung einer Instanz, die nicht in der Datenbank vorhanden ist, da eine Fehlersituation möglicherweise niemals ausgeführt wird, solange die Entität nicht vorhanden ist.In jedem Fall müssen Sie die Entität manipulieren, während die Sitzung geöffnet wird, um das Laden sicherzustellen. Sie können dies tun, indem Sie eine beliebige Methode für die Entität aufrufen.
Oder eine bessere alternative Verwendung
findById(ID id)
anstelle von.Warum eine so unklare API?
Zum Abschluss zwei Fragen an Spring-Data-JPA-Entwickler:
Warum nicht eine klarere Dokumentation für
getOne()
? Entity Lazy Loading ist wirklich kein Detail.Warum müssen Sie einführen, um
getOne()
zu wickelnEM.getReference()
?Warum nicht einfach bei der Wrapped-Methode bleiben :
getReference()
? Diese EM-Methode ist wirklich sehr speziell undgetOne()
vermittelt gleichzeitig eine so einfache Verarbeitung.quelle
getOne()
Verwendet verzögertes Laden und wirft ein,EntityNotFoundException
wenn kein Element gefunden wird.findById()
wird sofort geladen und gibt null zurück, wenn es nicht gefunden wird. Da es mit getOne () einige unvorhersehbare Situationen gibt, wird empfohlen, stattdessen findById () zu verwenden.Der grundlegende Unterschied ist, dass
getOne
faul geladen ist undfindOne
nicht.Betrachten Sie das folgende Beispiel:
quelle
1. Warum schlägt die Methode getOne (id) fehl?
Siehe diesen Abschnitt in den Dokumenten . Das Überschreiben der bereits vorhandenen Transaktion kann das Problem verursachen. Ohne weitere Informationen ist diese jedoch schwer zu beantworten.
2. Wann sollte ich die Methode getOne (id) verwenden?
Ohne sich mit den Interna von Spring Data JPA zu befassen, scheint der Unterschied in dem Mechanismus zu liegen, der zum Abrufen der Entität verwendet wird.
Wenn man sich die aussehen JavaDoc für
getOne(ID)
unter Siehe auch :Es scheint, dass diese Methode nur an die Implementierung des JPA-Entitätsmanagers delegiert.
In den Dokumenten für wird
findOne(ID)
dies jedoch nicht erwähnt.Der Hinweis liegt auch in den Namen der Repositories.
JpaRepository
ist JPA-spezifisch und kann daher bei Bedarf Anrufe an den Entitätsmanager delegieren.CrudRepository
ist agnostisch gegenüber der verwendeten Persistenztechnologie. Schau hier . Es wird als Markierungsschnittstelle für mehrere Persistenztechnologien wie JPA, Neo4J usw. verwendet.Es gibt also keinen wirklichen Unterschied zwischen den beiden Methoden für Ihre Anwendungsfälle. Es ist nur
findOne(ID)
allgemeiner als die spezialisierterengetOne(ID)
. Welches Sie verwenden, liegt bei Ihnen und Ihrem Projekt, aber ich würde mich persönlich an das halten,findOne(ID)
da es Ihren Code weniger implementierungsspezifisch macht und die Türen öffnet, um in Zukunft zu Dingen wie MongoDB usw. überzugehen, ohne zu viel umgestalten zu müssen :)quelle
there's not really a 'difference' in the two methods
hier zu sagen , da es wirklich einen großen Unterschied gibt, wie die Entität abgerufen wird und was Sie von der Methode erwarten sollten. Die Antwort weiter unten von @davidxxx hebt dies sehr gut hervor, und ich denke, jeder, der Spring Data JPA verwendet, sollte sich dessen bewusst sein. Andernfalls kann es zu Kopfschmerzen kommen.Die
getOne
Methoden geben nur die Referenz aus der Datenbank zurück (verzögertes Laden). Sie befinden sich also im Grunde genommen außerhalb der Transaktion (die, dieTransactional
Sie in der Serviceklasse deklariert haben, wird nicht berücksichtigt), und der Fehler tritt auf.quelle
Ich finde die obigen Antworten wirklich sehr schwierig. Aus Debugging-Sicht habe ich fast 8 Stunden damit verbracht, den dummen Fehler zu kennen.
Ich habe Spring + Winterschlaf + Bulldozer + MySQL-Projekt getestet. Deutlich sein.
Ich habe Benutzerentität, Buchentität. Sie führen die Mapping-Berechnungen durch.
Wurden die mehreren Bücher an einen Benutzer gebunden? Aber in UserServiceImpl habe ich versucht, es durch getOne (userId) zu finden;
Das Rest-Ergebnis ist
}}
Der obige Code hat die Bücher, die der Benutzer beispielsweise liest, nicht abgerufen.
Die bookList war wegen getOne (ID) immer null. Nach dem Wechsel zu findOne (ID). Das Ergebnis ist
}}
quelle
Während spring.jpa.open-in-view wahr war, hatte ich kein Problem mit getOne, aber nachdem ich es auf false gesetzt hatte, bekam ich LazyInitializationException. Dann wurde das Problem durch Ersetzen durch findById gelöst.
Obwohl es eine andere Lösung gibt, ohne die getOne-Methode zu ersetzen, und die @Transactional at-Methode ist, die repository.getOne (id) aufruft. Auf diese Weise existiert eine Transaktion und die Sitzung wird in Ihrer Methode nicht geschlossen, und während die Entität verwendet wird, gibt es keine LazyInitializationException.
quelle
Ich hatte ein ähnliches Problem beim Verstehen, warum JpaRespository.getOne (id) nicht funktioniert und einen Fehler auslöst.
Ich ging zu JpaRespository.findById (id), wo Sie eine Option zurückgeben müssen.
Dies ist wahrscheinlich mein erster Kommentar zu StackOverflow.
quelle