J'ai un modèle d'objet persistant JPA qui contient une relation plusieurs-à-un: an Account
a plusieurs Transactions
. A en Transaction
a un Account
.
Voici un extrait du 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;
Je peux créer un Account
objet, y ajouter des transactions et conserver Account
correctement l' objet. Mais, lorsque je crée une transaction, en utilisant un compte existant déjà persistant et en persistant la transaction , j'obtiens une exception:
Causée par: org.hibernate.PersistentObjectException: entité détachée passée pour persister: com.paulsanwald.Account à org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)
Donc, je suis capable de persister un Account
qui contient des transactions, mais pas une transaction qui a un Account
. Je pensais que c'était parce queAccount
peut ne pas être attaché, mais ce code me donne toujours la même exception:
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);
Comment puis-je enregistrer correctement un Transaction
, associé à un Account
objet déjà persistant ?
Réponses:
Il s'agit d'un problème de cohérence bidirectionnel typique. Il est bien discuté dans ce lien ainsi que ce lien.
Selon les articles des 2 liens précédents, vous devez corriger vos setters des deux côtés de la relation bidirectionnelle. Un exemple de setter pour le côté One est dans ce lien.
Un exemple de setter pour le côté Many se trouve dans ce lien.
Après avoir corrigé vos setters, vous voulez déclarer le type d'accès Entity comme "Property". La meilleure pratique pour déclarer le type d'accès "Propriété" consiste à déplacer TOUTES les annotations des propriétés de membre vers les getters correspondants. Un grand mot de prudence est de ne pas mélanger les types d'accès "Champ" et "Propriété" au sein de la classe d'entité sinon le comportement n'est pas défini par les spécifications JSR-317.
la source
detached entity passed to persist
pourquoi améliorer la cohérence fait que cela fonctionne? Ok, la cohérence a été réparée mais l'objet est toujours détaché.La solution est simple, utilisez simplement le
CascadeType.MERGE
au lieu deCascadeType.PERSIST
ouCascadeType.ALL
.J'ai eu le même problème et
CascadeType.MERGE
j'ai travaillé pour moi.J'espère que vous êtes trié.
la source
Supprimez la cascade de l'entité enfant
Transaction
, cela devrait être juste:(
FetchType.EAGER
peut être supprimé ainsi que sa valeur par défaut@ManyToOne
)C'est tout!
Pourquoi? En disant "cascade ALL" sur l'entité enfant,
Transaction
vous exigez que chaque opération de base de données soit propagée à l'entité parentAccount
. Si vous le faitespersist(transaction)
,persist(account)
sera également invoqué.Mais seules les (nouvelles) entités transitoires peuvent être transmises à
persist
(Transaction
dans ce cas). Les éléments détachés (ou autres états non transitoires) peuvent ne pas (Account
dans ce cas, car ils sont déjà dans DB).Par conséquent, vous obtenez l'exception "entité détachée passée pour persister" . L'
Account
entité est destinée! Pas celui queTransaction
vous appelezpersist
.Vous ne voulez généralement pas vous propager de l'enfant au parent. Malheureusement, il existe de nombreux exemples de code dans les livres (même dans les bons) et sur le net, qui font exactement cela. Je ne sais pas, pourquoi ... Peut-être parfois simplement copié encore et encore sans trop réfléchir ...
Devinez ce qui se passe si vous appelez
remove(transaction)
toujours "TOUT en cascade" dans ce @ManyToOne? Leaccount
(btw, avec toutes les autres transactions!) Sera également supprimé de la base de données. Mais ce n'était pas votre intention, n'est-ce pas?la source
L'utilisation de la fusion est risquée et délicate, c'est donc une solution de contournement sale dans votre cas. Vous devez vous rappeler au moins que lorsque vous passez un objet entité à fusionner, il cesse d' être attaché à la transaction et à la place une nouvelle entité désormais attachée est renvoyée. Cela signifie que si quelqu'un a toujours l'ancien objet entité en sa possession, les modifications apportées sont silencieusement ignorées et jetées lors de la validation.
Vous ne montrez pas le code complet ici, donc je ne peux pas revérifier votre modèle de transaction. Une façon d'arriver à une situation comme celle-ci est de ne pas avoir de transaction active lors de l'exécution de la fusion et de persister. Dans ce cas, le fournisseur de persistance doit ouvrir une nouvelle transaction pour chaque opération JPA que vous effectuez et la valider et la fermer immédiatement avant le retour de l'appel. Si tel est le cas, la fusion sera exécutée dans une première transaction, puis après le retour de la méthode de fusion, la transaction est terminée et fermée et l'entité retournée est maintenant détachée. La persistance en dessous ouvrirait alors une deuxième transaction et tenterait de faire référence à une entité qui est détachée, donnant une exception. Enveloppez toujours votre code dans une transaction, sauf si vous savez très bien ce que vous faites.
En utilisant une transaction gérée par conteneur, cela ressemblerait à quelque chose comme ceci. Remarque: cela suppose que la méthode se trouve dans un bean session et appelée via une interface locale ou distante.
la source
@Transaction(readonly=false)
couche de service ., Je reçois toujours le même problème,Dans ce cas, vous avez probablement obtenu votre
account
objet en utilisant la logique de fusion, etpersist
est utilisé pour conserver de nouveaux objets et il se plaindra si la hiérarchie a un objet déjà persistant. Vous devez utilisersaveOrUpdate
dans de tels cas, au lieu depersist
.la source
merge
sur l'transaction
objet que vous obtenez la même erreur?transaction
n'a pas persisté? Comment avez-vous vérifié. Notez quemerge
renvoie une référence à l'objet persistant.Ne passez pas id (pk) à la méthode persist ou essayez la méthode save () au lieu de persist ().
la source
TestEntityManager.persistAndFlush()
pour rendre une instance gérée et persistante, puis synchroniser le contexte de persistance avec la base de données sous-jacente. Renvoie l'entité source d'origineAfin de résoudre le problème, vous devez suivre ces étapes:
1. Suppression de l'association d'enfants en cascade
Vous devez donc supprimer le
@CascadeType.ALL
de l'@ManyToOne
association. Les entités enfants ne doivent pas se connecter en cascade aux associations de parents. Seules les entités parentes doivent se connecter en cascade aux entités enfants.Notez que j'ai défini l'
fetch
attribut sur,FetchType.LAZY
car une récupération rapide est très mauvaise pour les performances .2. Définir les deux côtés de l'association
Chaque fois que vous avez une association bidirectionnelle, vous devez synchroniser les deux côtés à l'aide des méthodes
addChild
etremoveChild
dans l'entité parent:Pour plus de détails sur les raisons pour lesquelles il est important de synchroniser les deux extrémités d'une association bidirectionnelle, consultez cet article .
la source
Dans votre définition d'entité, vous ne spécifiez pas la @JoinColumn pour le
Account
joint à aTransaction
. Vous voudrez quelque chose comme ça:EDIT: Eh bien, je suppose que ce serait utile si vous utilisiez l'
@Table
annotation sur votre classe. Il h. :)la source
Même si vos annotations sont déclarées correctement pour gérer correctement la relation un-à-plusieurs, vous pouvez toujours rencontrer cette exception précise. Lors de l'ajout d'un nouvel objet enfant
Transaction
, à un modèle de données attaché, vous devrez gérer la valeur de la clé primaire - sauf si vous n'êtes pas censé le faire . Si vous fournissez une valeur de clé primaire pour une entité enfant déclarée comme suit avant d'appelerpersist(T)
, vous rencontrerez cette exception.Dans ce cas, les annotations déclarent que la base de données gérera la génération des valeurs de clé primaire de l'entité lors de l'insertion. Fournir un vous-même (par exemple via le setter de l'ID) provoque cette exception.
Alternativement, mais en fait la même chose, cette déclaration d'annotation entraîne la même exception:
Donc, ne définissez pas la
id
valeur dans votre code d'application lorsqu'elle est déjà gérée.la source
Si rien ne vous aide et que vous obtenez toujours cette exception, passez en revue votre
equals()
méthodes - et n'incluez pas de collection d'enfant dans celle-ci. Surtout si vous avez une structure profonde de collections intégrées (par exemple, A contient Bs, B contient Cs, etc.).En exemple de
Account -> Transactions
:Dans l'exemple ci-dessus, supprimez les transactions des
equals()
chèques. En effet, hibernate impliquera que vous n'essayez pas de mettre à jour l'ancien objet, mais que vous passez un nouvel objet pour qu'il persiste, chaque fois que vous changez d'élément sur la collection enfant.Bien sûr, ces solutions ne conviennent pas à toutes les applications et vous devez soigneusement concevoir ce que vous souhaitez inclure dans les méthodes
equals
ethashCode
.la source
C'est peut-être le bogue d'OpenJPA, lors de la restauration, il réinitialise le champ @Version, mais le pcVersionInit reste vrai. J'ai une AbstraceEntity qui a déclaré le champ @Version. Je peux le contourner en réinitialisant le champ pcVersionInit. Mais ce n'est pas une bonne idée. Je pense que cela ne fonctionne pas lorsque la cascade persiste.
la source
Vous devez définir la transaction pour chaque compte.
Ou il peut suffire (le cas échéant) de définir les identifiants sur null de plusieurs côtés.
la source
la source
Ma réponse basée sur Spring Data JPA: j'ai simplement ajouté une
@Transactional
annotation à ma méthode externe.Pourquoi ça marche
L'entité enfant se détachait immédiatement car il n'y avait pas de contexte de session Hibernate actif. La fourniture d'une transaction Spring (Data JPA) garantit la présence d'une session Hibernate.
Référence:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
la source
Dans mon cas, je commettais une transaction lorsque la méthode persist était utilisée. En changeant la méthode persist to save , elle a été résolue.
la source
Si les solutions ci-dessus ne fonctionnent pas une seule fois, commentez les méthodes getter et setter de la classe d'entité et ne définissez pas la valeur de id. (Clé primaire) Ensuite, cela fonctionnera.
la source
@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) a fonctionné pour moi.
la source
Résolu en enregistrant l'objet dépendant avant le suivant.
Cela m'est arrivé parce que je ne définissais pas l'ID (qui n'était pas généré automatiquement). et essayer d'économiser avec la relation @ManytoOne
la source