Ich habe ein JPA-beständiges Objektmodell, das eine Viele-zu-Eins-Beziehung enthält: Ein Account
hat viele Transactions
. A Transaction
hat einen Account
.
Hier ist ein Ausschnitt aus dem Code:
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
Ich kann ein Account
Objekt erstellen , Transaktionen hinzufügen und das Account
Objekt korrekt beibehalten. Wenn ich jedoch eine Transaktion erstelle, ein bereits bestehendes Konto verwende und die Transaktion beibehalten habe , erhalte ich eine Ausnahme:
Auslöser: org.hibernate.PersistentObjectException: Abgetrennte Entität, die an persist übergeben wurde: com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)
Ich kann Account
also eine Transaktion beibehalten , die Transaktionen enthält, aber keine Transaktion mit einer Account
. Ich dachte, das lag daran, dass das Account
möglicherweise nicht angehängt ist, aber dieser Code gibt mir immer noch die gleiche Ausnahme:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
Wie kann ich ein Transaction
mit einem bereits persistierten Account
Objekt verknüpftes Objekt korrekt speichern ?
Antworten:
Dies ist ein typisches bidirektionales Konsistenzproblem. Es ist in besprochen diesem Link sowie .
Gemäß den Artikeln in den vorherigen 2 Links müssen Sie Ihre Setter auf beiden Seiten der bidirektionalen Beziehung reparieren. Ein Beispiel für die One-Seite befindet sich in diesem Link.
Ein Beispiel für die Seite "Viele" finden Sie in diesem Link.
Nachdem Sie Ihre Setter korrigiert haben, möchten Sie den Entity-Zugriffstyp als "Eigenschaft" deklarieren. Die bewährte Methode zum Deklarieren des Zugriffstyps "Eigenschaft" besteht darin, ALLE Anmerkungen aus den Elementeigenschaften in die entsprechenden Getter zu verschieben. Ein großes Wort der Vorsicht ist, keine Zugriffstypen "Feld" und "Eigenschaft" innerhalb der Entitätsklasse zu mischen, da sonst das Verhalten in den JSR-317-Spezifikationen nicht definiert ist.
quelle
detached entity passed to persist
Warum funktioniert die Verbesserung der Konsistenz? Ok, die Konsistenz wurde repariert, aber das Objekt ist immer noch getrennt.Die Lösung ist einfach, verwenden Sie einfach die
CascadeType.MERGE
anstelle vonCascadeType.PERSIST
oderCascadeType.ALL
.Ich hatte das gleiche Problem und
CascadeType.MERGE
habe für mich gearbeitet.Ich hoffe du bist sortiert.
quelle
Entfernen Sie die Kaskadierung aus der untergeordneten Entität
Transaction
. Es sollte nur Folgendes sein:(
FetchType.EAGER
kann entfernt werden, ebenso wie die Standardeinstellung für@ManyToOne
)Das ist alles!
Warum? Wenn
Transaction
Sie für die untergeordnete Entität "cascade ALL" sagen, müssen Sie sicherstellen, dass jede DB-Operation an die übergeordnete Entität weitergegeben wirdAccount
. Wenn Sie dies dann tunpersist(transaction)
,persist(account)
wird auch aufgerufen.Es dürfen jedoch nur vorübergehende (neue) Entitäten übergeben werden
persist
(Transaction
in diesem Fall). Die getrennten (oder anderen nicht vorübergehenden) Zustände können dies nicht (Account
in diesem Fall, da es sich bereits in der Datenbank befindet).Daher erhalten Sie die Ausnahme "getrennte Entität übergeben, um zu bestehen" . Die
Account
Entität ist gemeint! Nicht das, wasTransaction
du anrufstpersist
.Sie möchten im Allgemeinen nicht vom Kind zum Elternteil weitergeben. Leider gibt es viele Codebeispiele in Büchern (auch in guten) und im Internet, die genau das tun. Ich weiß nicht warum ... Vielleicht manchmal einfach immer und immer wieder kopiert, ohne viel nachzudenken ...
remove(transaction)
Ratet mal, was passiert, wenn Sie immer noch "Cascade ALL" in diesem @ManyToOne haben? Dieaccount
(übrigens mit allen anderen Transaktionen!) Werden ebenfalls aus der DB gelöscht. Aber das war nicht deine Absicht, oder?quelle
Die Verwendung von Merge ist riskant und schwierig, daher ist es in Ihrem Fall eine schmutzige Problemumgehung. Sie müssen zumindest daran zu erinnern , dass , wenn Sie eine Entität Objekt zu verschmelzen passieren, ist es nicht mehr auf die Transaktion befestigt ist und stattdessen eine neue, jetzt-gebundene Einheit zurückgeführt wird. Dies bedeutet, dass, wenn jemand das alte Entitätsobjekt noch in seinem Besitz hat, Änderungen daran stillschweigend ignoriert und beim Festschreiben weggeworfen werden.
Sie zeigen hier nicht den vollständigen Code an, daher kann ich Ihr Transaktionsmuster nicht überprüfen. Eine Möglichkeit, zu einer solchen Situation zu gelangen, besteht darin, dass beim Ausführen der Zusammenführung und Fortbestehen keine Transaktion aktiv ist. In diesem Fall wird erwartet, dass der Persistenzanbieter für jede von Ihnen ausgeführte JPA-Operation eine neue Transaktion öffnet und diese sofort festschreibt und schließt, bevor der Anruf zurückkehrt. In diesem Fall wird die Zusammenführung in einer ersten Transaktion ausgeführt. Nachdem die Zusammenführungsmethode zurückgegeben wurde, wird die Transaktion abgeschlossen und geschlossen, und die zurückgegebene Entität wird nun getrennt. Das darunter liegende Persist würde dann eine zweite Transaktion öffnen und versuchen, auf eine Entität zu verweisen, die getrennt ist, was eine Ausnahme darstellt. Wickeln Sie Ihren Code immer in eine Transaktion ein, es sei denn, Sie wissen genau, was Sie tun.
Bei Verwendung einer von einem Container verwalteten Transaktion würde dies ungefähr so aussehen. Beachten Sie: Dies setzt voraus, dass sich die Methode in einer Session-Bean befindet und über die lokale oder Remote-Schnittstelle aufgerufen wird.
quelle
@Transaction(readonly=false)
auf der Service-Ebene verwendet. IchWahrscheinlich haben Sie in diesem Fall Ihr
account
Objekt mithilfe der Zusammenführungslogik erhalten undpersist
werden verwendet, um neue Objekte beizubehalten. Es wird sich beschweren, wenn die Hierarchie ein bereits bestehendes Objekt enthält. Sie solltensaveOrUpdate
in solchen Fällen anstelle von verwendenpersist
.quelle
merge
auftransaction
Objekt , das Sie die gleichen Fehler?transaction
das nicht bestanden wurde? Wie hast du das überprüft? Beachten Sie, dassmerge
ein Verweis auf das persistierte Objekt zurückgegeben wird.Übergeben Sie id (pk) nicht an persist method oder versuchen Sie save () method anstelle von persist ().
quelle
TestEntityManager.persistAndFlush()
auch eine Instanz verwalten und persistent machen und dann den Persistenzkontext mit der zugrunde liegenden Datenbank synchronisieren. Gibt die ursprüngliche Quellentität zurückUm das Problem zu beheben, müssen Sie die folgenden Schritte ausführen:
1. Entfernen der untergeordneten Kaskadierung
Sie müssen also die
@CascadeType.ALL
aus der@ManyToOne
Zuordnung entfernen . Untergeordnete Entitäten sollten nicht zu übergeordneten Zuordnungen kaskadieren. Nur übergeordnete Entitäten sollten zu untergeordneten Entitäten kaskadieren.Beachten Sie, dass ich das
fetch
Attribut auf gesetzt habe,FetchType.LAZY
da eifriges Abrufen die Leistung sehr beeinträchtigt .2. Legen Sie beide Seiten der Zuordnung fest
Wenn Sie eine bidirektionale Zuordnung haben, müssen Sie beide Seiten mit
addChild
undremoveChild
Methoden in der übergeordneten Entität synchronisieren :Weitere Informationen dazu, warum es wichtig ist, beide Enden einer bidirektionalen Zuordnung zu synchronisieren, finden Sie in diesem Artikel .
quelle
In Ihrer Entitätsdefinition geben Sie nicht die @ JoinColumn für die Verknüpfung
Account
mit a anTransaction
. Du wirst so etwas wollen:EDIT: Nun, ich denke, das wäre nützlich, wenn Sie die
@Table
Anmerkung für Ihre Klasse verwenden würden. Heh. :) :)quelle
Selbst wenn Ihre Anmerkungen korrekt deklariert sind, um die Eins-zu-Viele-Beziehung ordnungsgemäß zu verwalten, kann diese genaue Ausnahme auftreten. Wenn
Transaction
Sie einem angehängten Datenmodell ein neues untergeordnetes Objekt hinzufügen, müssen Sie den Primärschlüsselwert verwalten - es sei denn, Sie sollten dies nicht tun . Wenn Sie vor dem Aufruf einen Primärschlüsselwert für eine untergeordnete Entität angeben, die wie folgt deklariert ist,persist(T)
tritt diese Ausnahme auf.In diesem Fall wird in den Anmerkungen angegeben, dass die Datenbank die Generierung der Primärschlüsselwerte der Entität beim Einfügen verwaltet. Wenn Sie selbst einen bereitstellen (z. B. über den ID-Setter), wird diese Ausnahme verursacht.
Alternativ, aber effektiv gleich, führt diese Anmerkungsdeklaration zu derselben Ausnahme:
Legen Sie den
id
Wert in Ihrem Anwendungscode also nicht fest, wenn er bereits verwaltet wird.quelle
Wenn nichts hilft und Sie immer noch diese Ausnahme erhalten, überprüfen Sie Ihre
equals()
Methoden - und fügen Sie keine untergeordnete Sammlung hinzu. Besonders wenn Sie eine tiefe Struktur eingebetteter Sammlungen haben (z. B. A enthält Bs, B enthält Cs usw.).Im Beispiel von
Account -> Transactions
:Entfernen Sie im obigen Beispiel Transaktionen aus
equals()
Schecks. Dies liegt daran, dass der Ruhezustand bedeutet, dass Sie nicht versuchen, ein altes Objekt zu aktualisieren, sondern ein neues Objekt übergeben, um es beizubehalten, wenn Sie ein Element in der untergeordneten Sammlung ändern.Natürlich passen diese Lösungen nicht zu allen Anwendungen, und Sie sollten sorgfältig entwerfen, was Sie in die
equals
undhashCode
-Methoden aufnehmen möchten .quelle
Vielleicht ist es der Fehler von OpenJPA. Beim Rollback wird das @ Versionsfeld zurückgesetzt, aber das pcVersionInit bleibt wahr. Ich habe eine AbstraceEntity, die das Feld @Version deklariert hat. Ich kann es umgehen, indem ich das Feld pcVersionInit zurücksetze. Aber es ist keine gute Idee. Ich denke, es funktioniert nicht, wenn die Kaskade bestehen bleibt.
quelle
Sie müssen die Transaktion für jedes Konto festlegen.
Oder es reicht aus (falls zutreffend), IDs auf vielen Seiten auf null zu setzen.
quelle
quelle
In meinem Fall habe ich eine Transaktion festgeschrieben, als die Persist- Methode verwendet wurde. Beim Ändern von persist to save wurde die Methode behoben.
quelle
Wenn die oben genannten Lösungen nicht nur einmal funktionieren, kommentieren Sie die Getter- und Setter-Methoden der Entitätsklasse und setzen Sie nicht den Wert von id (Primärschlüssel). Dies funktioniert dann.
quelle
@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) hat für mich gearbeitet.
quelle
Meine JPA-basierte Antwort auf Spring Data: Ich
@Transactional
habe meiner äußeren Methode einfach eine Anmerkung hinzugefügt .Warum es funktioniert
Die untergeordnete Entität wurde sofort getrennt, da kein aktiver Sitzungskontext für den Ruhezustand vorhanden war. Durch das Bereitstellen einer Spring-Transaktion (Data JPA) wird sichergestellt, dass eine Ruhezustandssitzung vorhanden ist.
Referenz:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
quelle
Wird behoben, indem abhängige Objekte vor dem nächsten gespeichert werden.
Dies ist mir passiert, weil ich keine ID festgelegt habe (die nicht automatisch generiert wurde). und versuchen, mit der Beziehung @ManytoOne zu speichern
quelle