Die Frage ist im Grunde die gleiche wie unten:
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.
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.
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.
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.