Bei Verwendung der Methoden getOne und findOne Spring Data JPA

154

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 @Transactionalhat 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 getOneMethode, 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:

  1. Warum die getOne(id)Methode nicht bestehen?
  2. 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.

Manuel Jordan
quelle
Sie sollten getOne () insbesondere nicht verwenden, um die Existenz eines Objekts in der Datenbank zu testen, da Sie mit getOne immer ein Objekt erhalten! = Null, während findOne null liefert.
Uwe Allner

Antworten:

135

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.0Version Spring-Data-Jpageändert findOne().
Zuvor wurde es in der CrudRepositorySchnittstelle wie folgt definiert :

T findOne(ID primaryKey);

Die einzige findOne()Methode, die Sie finden, CrudRepositoryist die in der QueryByExampleExecutorBenutzeroberfläche definierte :

<S extends T> Optional<S> findOne(Example<S> example);

Dies wird schließlich durch SimpleJpaRepositorydie Standardimplementierung der CrudRepositorySchnittstelle 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 EntityManagerMethode 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 EntityManagerMethode deklariert als:

public <T> T getReference(Class<T> entityClass,
                              Object primaryKey);

Und zum Glück hat der EntityManagerJavadoc 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 nullReferenz als serialisiertes Ergebnis erhalten, oder wenn eine Methode für das Proxy-Objekt aufgerufen wird, wird eine Ausnahme LazyInitializationExceptionausgelöst.
In solchen Situationen ist der Wurf der EntityNotFoundExceptionHauptgrund 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.

davidxxx
quelle
3
Ich war verwirrt, warum getOne () die EntityNotFoundException nicht auslöst, aber Ihre "EntityNotFoundException wird ausgelöst, wenn der Instanzstatus zum ersten Mal aufgerufen wird" erklärte mir das Konzept. Danke
TheCoder
Zusammenfassung dieser Antwort: getOne()Verwendet verzögertes Laden und wirft ein, EntityNotFoundExceptionwenn 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.
Janac Meena
124

Der grundlegende Unterschied ist, dass getOnefaul geladen ist und findOnenicht.

Betrachten Sie das folgende Beispiel:

public static String NON_EXISTING_ID = -1;
...
MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID);
MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID);

if(findEnt != null) {
     findEnt.getText(); // findEnt is null - this code is not executed
}

if(getEnt != null) {
     getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!!
}
Marek Halmo
quelle
1
bedeutet nicht faul geladen, dass es nur geladen wird, wenn die Entität verwendet werden soll? Ich würde also erwarten, dass getEnt null ist und der Code innerhalb der Sekunde, wenn er nicht ausgeführt wird. Könnten Sie das bitte erklären? Vielen Dank!
Doug
Wenn es in einen CompletableFuture <> -Webdienst eingebunden ist, habe ich festgestellt, dass Sie findOne () vs. getOne () verwenden möchten, da es sich um eine verzögerte Implementierung handelt.
Fratt
76

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 :

See Also:
EntityManager.getReference(Class, Object)

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. JpaRepositoryist JPA-spezifisch und kann daher bei Bedarf Anrufe an den Entitätsmanager delegieren. CrudRepositoryist 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 spezialisierteren getOne(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 :)

Donovan Müller
quelle
Vielen Dank, Donovan, hat Sinn Ihre Antwort.
Manuel Jordan
20
Ich denke, es ist sehr irreführend, dies there's not really a 'difference' in the two methodshier 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.
Fridberg
16

Die getOneMethoden geben nur die Referenz aus der Datenbank zurück (verzögertes Laden). Sie befinden sich also im Grunde genommen außerhalb der Transaktion (die, die TransactionalSie in der Serviceklasse deklariert haben, wird nicht berücksichtigt), und der Fehler tritt auf.

Bogdan Mata
quelle
Scheint, als ob EntityManager.getReference (Klasse, Objekt) "nichts" zurückgibt, da wir uns in einem neuen Transaktionsbereich befinden.
Manuel Jordan
2

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;

public UserDTO getById(int userId) throws Exception {

    final User user = userDao.getOne(userId);

    if (user == null) {
        throw new ServiceException("User not found", HttpStatus.NOT_FOUND);
    }
    userDto = mapEntityToDto.transformBO(user, UserDTO.class);

    return userDto;
}

Das Rest-Ergebnis ist

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 1,
        "name": "TEST_ME",
        "bookList": null
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}}

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

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 0,
        "name": "Annama",
        "bookList": [
            {
                "id": 2,
                "book_no": "The karma of searching",
            }
        ]
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}}

EngineSense
quelle
-1

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.

Farshad Falaki
quelle
-2

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.

akshaymittal143
quelle
Leider liefert und beantwortet dies weder die Frage noch verbessert es die vorhandenen Antworten.
JSTL
Ich verstehe, kein Problem.
Akshaymittal143