it-swarm.com.de

Spring-Data JPA: Neue Entität speichern, die auf eine vorhandene verweist

Die Frage ist im Grunde die gleiche wie unten:

JPA-Kaskade bleibt bestehen und Verweise auf getrennte Entitäten lösen PersistentObjectException aus. Warum?

Ich erstelle eine neue Entität, die auf eine vorhandene, getrennte verweist. Wenn ich diese Entität jetzt in meinem Spring-Daten-Repository speichere, wird eine Ausnahme ausgelöst:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist

wenn wir uns die save () -Methode im Quellcode von Spring Data JPA ansehen, sehen wir:

public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

und wenn wir uns isNew () in AbstractEntityInformation ansehen

public boolean isNew(T entity) {

    return getId(entity) == null;
}

Also im Grunde, wenn ich eine neue Entität speichern () (id == null), spring data wird immer persist aufrufen und daher wird dieses Szenario immer fehlschlagen.

Dies scheint ein sehr typischer Anwendungsfall beim Hinzufügen neuer Elemente zu Sammlungen zu sein.

Wie kann ich das beheben?

EDIT 1:

HINWEIS:

Dieses Problem steht NICHT in direktem Zusammenhang mit Speichern einer neuen Entität, die auf eine vorhandene Entität in Spring JPA verweist? . Um dies näher zu erläutern, erhalten Sie die Anforderung, die neue Entität über http zu erstellen. Anschließend extrahieren Sie die Informationen aus der Anforderung und erstellen Ihre Entität und die vorhandene Entität, auf die verwiesen wird. Daher werden sie immer losgelöst sein.

30
beginner_

Das Beste, was ich mir ausgedacht habe, ist

public final T save(T containable) {
    // if entity containable.getCompound already exists, it
    // must first be reattached to the entity manager or else
    // an exception will occur (issue in Spring Data JPA ->
    // save() method internal calls persists instead of merge)
    if (containable.getId() == null
            && containable.getCompound().getId() != null){
        Compound compound = getCompoundService()
                .getById(containable.getCompound().getId());
        containable.setCompound(compound);   
    }
    containable = getRepository().save(containable);
    return containable; 
}

Wir prüfen, ob wir in der problematischen Situation sind, und laden die vorhandene Entität mit ihrer ID erneut aus der Datenbank und setzen das Feld der neuen Entität auf diese frisch geladene Instanz. Es wird dann angehängt.

Dies erfordert, dass der Dienst für neue Entität einen Verweis auf den Dienst der Entität enthält, auf die verwiesen wird. Dies sollte kein Problem sein, da Sie ohnehin spring verwenden, sodass der Dienst einfach als neues @Autowired - Feld hinzugefügt werden kann.

Ein weiteres Problem (in meinem Fall ist dieses Verhalten tatsächlich erwünscht) ist jedoch, dass Sie die referenzierte vorhandene Entität nicht gleichzeitig ändern können, während Sie die neue speichern. Alle diese Änderungen werden ignoriert.

WICHTIGE NOTIZ:

In vielen und wahrscheinlich auch in Ihren Fällen kann dies viel einfacher sein. Sie können Ihrem Service eine Referenz des Entitätsmanagers hinzufügen:

@PersistenceContext
private EntityManager entityManager;

und im obigen if(){} Block verwenden

containable = entityManager.merge(containable);

anstelle von meinem Code (ungetestet, wenn es funktioniert).

In meinem Fall sind die Klassen abstrakt und targetEntity in @ManyToOne Ist daher auch abstrakt. Der direkte Aufruf von entityManager.merge (containable) führt dann zu einer Ausnahme. Wenn Ihre Klassen jedoch ganz konkret sind, sollte dies funktionieren.

9
beginner_

Ich hatte ein ähnliches Problem, bei dem ich versuchte, ein neues Entitätsobjekt mit einem bereits gespeicherten Entitätsobjekt darin zu speichern.

Was ich getan habe, wurde implementiert Persistable <T> und isNew () entsprechend implementiert.

public class MyEntity implements Persistable<Long> {

    public boolean isNew() {
        return null == getId() &&
            subEntity.getId() == null;
    }

Oder Sie könnten AbstractPersistable verwenden und das natürlich isNew there überschreiben.

Ich weiß nicht, ob dies eine gute Möglichkeit ist, mit diesem Problem umzugehen, aber es hat für mich ganz gut geklappt und fühlt sich außerdem sehr natürlich an.

12
Jonas Geiregat

Ich habe das gleiche Problem mit einem @EmbeddedId Und Geschäftsdaten als Teil der ID.
Die einzige Möglichkeit, festzustellen, ob die Entität neu ist, besteht darin, einen (em.find(entity)==null)?em.persist(entity):em.merge(entity)

spring-data bietet jedoch nur die Methode save() und es gibt keine Möglichkeit, die Methode Persistable.isNew() mit einer Methode find() zu füllen.

3
nakkun