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 als getOne()
.
Also in den meisten Fällen lieber findOne()/findById()
als getOne()
.
API-Änderung
Zumindest von der 2.0
Version Spring-Data-Jpa
geändert findOne()
.
Zuvor wurde es in der CrudRepository
Schnittstelle wie folgt definiert :
T findOne(ID primaryKey);
Die einzige findOne()
Methode, die Sie finden, CrudRepository
ist die in der QueryByExampleExecutor
Benutzeroberfläche definierte :
<S extends T> Optional<S> findOne(Example<S> example);
Dies wird schließlich durch SimpleJpaRepository
die Standardimplementierung der CrudRepository
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
Optional<T> findById(ID id);
Jetzt wird ein zurückgegeben Optional
. Welches ist nicht so schlimm zu verhindern NullPointerException
.
Die eigentliche Wahl liegt nun zwischen Optional<T> findById(ID id)
und T 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:
Ruft eine Entität anhand ihrer ID ab.
Wenn wir uns die Implementierung ansehen, können wir sehen, dass sie EntityManager.find()
für den Abruf erforderlich ist:
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}
Und hier em.find()
ist eine EntityManager
Methode deklariert als:
public <T> T find(Class<T> entityClass, Object primaryKey,
Map<String, Object> properties);
Sein Javadoc sagt:
Suchen nach Primärschlüssel unter Verwendung der angegebenen Eigenschaften
Das Abrufen einer geladenen Entität scheint also zu erwarten.
2) Während der T getOne(ID id)
Javadoc sagt (Schwerpunkt liegt bei mir):
Gibt einen Verweis auf die Entität mit dem angegebenen Bezeichner zurück.
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:
@Override
public T getOne(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
Hier em.getReference()
ist eine EntityManager
Methode deklariert als:
public <T> T getReference(Class<T> entityClass,
Object primaryKey);
Und zum Glück hat der EntityManager
Javadoc seine Absicht besser definiert (Schwerpunkt liegt bei mir):
Holen Sie sich eine Instanz, deren Status möglicherweise träge abgerufen wird . Wenn die angeforderte Instanz nicht in der Datenbank vorhanden ist, wird beim ersten Zugriff auf den Instanzstatus die EntityNotFoundException ausgelöst . (Die Laufzeit des Persistenzanbieters darf die EntityNotFoundException auslösen, wenn getReference aufgerufen wird.) Die Anwendung sollte nicht erwarten, dass der Instanzstatus beim Trennen verfügbar ist , es sei denn, die Anwendung hat auf ihn zugegriffen, während der Entitätsmanager geöffnet war.
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 Ausnahme LazyInitializationException
ausgelöst.
In solchen Situationen ist der Wurf der EntityNotFoundException
Hauptgrund für die getOne()
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 wickeln EM.getReference()
?
Warum nicht einfach bei der Wrapped-Methode bleiben : getReference()
? Diese EM-Methode ist wirklich sehr speziell und getOne()
vermittelt gleichzeitig eine so einfache Verarbeitung.