3
votes

Spring JPA OneToOne FK en tant que PK Cascade.

J'ai deux tables, b et a :

  • ils ont une relation bidirectionnelle un à un
  • a a une clé étrangère vers b qui définit cette relation
  • cette clé étrangère est également considérée comme une clé primaire pour un , et un JPA @ID
  • Je souhaite une suppression en cascade qui supprime le b associé lorsque a est supprimé
  • dans MySQL, le b_id de a est NOT NULL

Le problème est que lorsque je supprime mon objet A avec le référentiel JPA, j'obtiens une ConstraintViolationException sur sa clé étrangère. Je m'attendrais à ce que les lignes a et b soient supprimées (en commençant intelligemment par celle de a ).

Comment pourrais-je contourner ce problème en sachant que je veux conserver:

  • mon schéma DB est identique
  • la suppression en cascade de a vers b
  • l'ID b étant le JPA @Id pour a
@Entity
@Table(name = "b")
public class B {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "dbid")
    private Integer id;

    @OneToOne(mappedBy = "b")
    private A a;
}
@Entity
@Table(name = "a")
public class A {

    @Id
    @Column(name = "b_id")
    @GeneratedValue(generator = "gen")
    @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name="property", value="b"))
    private Integer bId;

    @OneToOne(cascade = CascadeType.REMOVE)
    @PrimaryKeyJoinColumn
    private B b;
}
CREATE TABLE `b` (
  `dbid` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`dbid`),
);

CREATE TABLE `a` (
  `b_id` int(11) NOT NULL,
  KEY `b_fk` (`b_id`),
  CONSTRAINT `b_fk` FOREIGN KEY (`b_id`) REFERENCES `b` (`dbid`),
);

[EDIT] Après toutes les discussions en réponse aux commentaires et relecture de ma question, les propositions avec orphanRemoval sont en effet dans la portée et le travail.


5 commentaires

Pour vous aider à diagnostiquer le problème, veuillez fournir SHOW CREATE TABLE pour chacun des deux tableaux.


Vous avez raison @RickJames, c'est plus clair, c'est fait.


Merci. Maintenant, veuillez justifier la nécessité d'un mappage 1: 1 entre une paire de tables. C'est presque toujours une "mauvaise conception de schéma" au début du développement. C'est généralement un kludge pour l'opportunité ou la performance lorsqu'il est fait plus tard dans le développement.


@RickJames Je ne veux pas expliquer le contexte complet de cette conception (pas du tout au début du développement), de toute façon c'est une des hypothèses de la question. Compte tenu de cela, puis-je atteindre la conception JPA que je cible?


Je préfère faire des suppressions en cascade (etc.) dans mon propre code. De cette façon, je n'ai pas à me demander si les FK "feront la bonne chose" ou "ne pourront pas faire cette cascade complexe".


5 Réponses :


0
votes

Pouvez-vous essayer dans la classe B d'ajouter le

@OneToOne(mappedBy = "b", cascade = CascadeType.REMOVE)
private A a;

De plus, si dans la base de données vous n'avez qu'une clé étrangère "a a une clé étrangère vers b" pouvez-vous également faire une clé étrangère de b à a également.


2 commentaires

Peut-être que le texte de ma question n'était pas assez clair, je l'ai juste édité, mais je veux la suppression en cascade de B à A et non l'inverse. Je ne veux pas non plus modifier mon schéma. Merci pour l'intérêt.


Désolé, mon erreur dans le commentaire ci-dessus, je veux vraiment une cascade de A à B , ce qui signifie que la suppression d'un A devrait supprimer le A associé code> B .



0
votes

essayez avec le code ci-dessous -

@OneToOne(mappedBy = "b",cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval=true )
private A a;


5 commentaires

Non, relisez la question, je veux la suppression en cascade de a à b et orphanRemoval est hors de propos.


dans la réponse ci-dessous, vous avez répondu exactement le contraire. pourriez-vous poster le code de suppression.


lisez également le lien concernant l'utilisation de Cascade.Remove et ophanRemoval . Vous devrez peut-être utiliser orphanRemoval selon la manière dont vous effectuez l'opération de suppression.


- orphanRemoval est très clair pour moi. Certains de mes B peuvent exister sans A , donc je ne veux pas orphanRemoval. - dans l'autre réponse, j'ai fait une erreur dans mon commentaire, le texte de ma question est valide, je souhaite une suppression en cascade de A à B , c'est-à-dire que la suppression d'un A doit supprimer le B associé


Je crois qu'avec votre code actuel, vous ne créez pas de valeurs dans le tableau A, si vous persistez les données dans le tableau B. Maintenant que vous venez à votre problème, vous pouvez toujours réaliser ce dont vous avez besoin en utilisant la propriété orphanRemoval à la table La fin de A car elle ne supprimera que ce dont vous avez besoin. Il supprimera uniquement les données associées du tableau A du tableau B.Le tableau B peut toujours avoir ses propres données car nous ne faisons pas de nouvelles entrées dans le tableau A.



1
votes

Si vous souhaitez supprimer l'objet de B , chaque fois que le A associé est supprimé (c'est le quatrième point de votre wishlist:

Je souhaite une suppression en cascade qui supprime le b associé lorsque a est supprimé

alors vous devez changer votre mappage dans A en:

@OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
@PrimaryKeyJoinColumn
private B b;


7 commentaires

Comme déjà indiqué dans la réponse de @ Narendra, ce n'est pas ce que je veux, certains de mes B peuvent exister sans A, donc je ne veux pas de orphanRemoval . De plus, cela ne résout pas le problème principal qu'est la levée de ConstraintViolationException .


Est-ce que je comprends clairement - vous ne voulez pas ce que vous avez spécifié dans votre question?


@Andronicus, votre solution est correcte. La seule chose qui manque est ON DELETE CASCADE dans le tableau a.


@PierreMardon s'il vous plaît, acceptez une réponse à votre question et ajoutez-en une autre avec la bonne spécification, vous ne faites que confondre les autres.


Il n'y a pas de bonne réponse à ma question pour le moment, peut-être celle d'Alan Hay que j'essaierai bientôt. Vous supposez que orphanRemoval est applicable mais ce comportement très spécifique est complètement hors de la portée de cette question.


Je suis désolé pour ma propre confusion, orphanRemoval est une proposition pertinente, et cela fonctionne bien. Je préférerais que la cascade fonctionne seule, mais votre réponse est toujours dans la portée.


Pas de problème, je suis content d'avoir pu aider :)



1
votes

Afin de réaliser ce que vous avez demandé, j'ai peaufiné vos tableaux comme suit:

@Entity
@Table(name = "a")
public class A {

    @Id
    @Column(name = "b_id")
    @GeneratedValue(generator = "gen")
    @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name="property", value="b"))
    private Integer bId;

    @OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
    @PrimaryKeyJoinColumn
    private B b;
}

CASCADE DELETE n'a pas été ajouté dans votre DDL. p>

Cela activera la suppression en cascade. Pour supprimer l'enregistrement b lors de la suppression de a , j'ai effectué les modifications suivantes dans la classe A:

    CREATE TABLE b (
       dbid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
    );

    CREATE TABLE a ( 
       b_id int(11) NOT NULL PRIMARY KEY REFERENCES b(dbid) ON DELETE CASCADE
    );

Cela a bien fonctionné. J'espère que cela aide.

Trouvez ici le lien vers la solution fonctionnelle.

p >


4 commentaires

La suppression en cascade JPA n'a absolument aucune obligation de définir une suppression en cascade au niveau de la base de données.


Vous avez résolu le problème par des moyens complètement différents - en supprimant B au niveau de la base de données et non via Entity Manager (via cascade). De telles opérations peuvent avoir des effets secondaires: données incohérentes en session, pas de cascades en aval (déclenchées par JPA) de B vers, disons, C, données incohérentes dans le cache de 2e niveau, pas d'exécution des écouteurs d'entité pré-suppression définis pour B, etc.


D'accord avec @AlanHay


@AlanHay, acceptez que dans une telle situation, cela pourrait poser problème si le cache de deuxième niveau est utilisé. L'utilisation du cache de deuxième niveau est toujours discutable. Il faut être prudent lors de l'utilisation du cache de deuxième niveau. Cette solution n'a pas été fournie en ce qui concerne la mise en cache. Ni l'un ni l'autre n'est inclus dans la question. Oui, l'utilisation de la cascade peut être évitée ici. L'élimination des orphelins fait la magie ici. À votre santé!



2
votes

En ce qui concerne uniquement le côté MySQL de votre implémentation , les enregistrements de la table B n'ont aucune "connaissance" des enregistrements de la table A. Dans la base de données, la relation est unidirectionnelle

La fonctionnalité de cascade native existe pour empêcher les erreurs de clé étrangère, en indiquant à la base de données ce qu'il faut faire lorsque la suppression d'un enregistrement ne laisserait une clé étrangère pointant nulle part. La suppression d'un enregistrement de table A ne provoquerait pas d'erreur de clé étrangère dans les enregistrements de table B, donc aucune fonctionnalité de cascade native ne serait déclenchée

Pour réitérer; Vous ne pouvez pas conserver le même schéma et la suppression en cascade de a à b , car vous ne disposez pas réellement de la suppression en cascade de a < / code> à b

Vous avez également mentionné dans les commentaires que certains enregistrements de la table B peuvent exister sans enregistrements de la table A qui ne figurent pas dans la question d'origine

Pour obtenir la suppression automatique des enregistrements de la table B que vous décrivez, vous avez quelques options en ce qui concerne la base de données:

  1. Swap the relation over - Supprimez la clé étrangère actuelle et ajoutez une colonne de clé étrangère Nullable dans la table B qui fait référence à la clé primaire de la table A. Vous pouvez ensuite mettre une suppression en cascade sur cette clé étrangère . Gardez la nouvelle colonne nulle pour les enregistrements de table B qui n'appartiennent pas à un enregistrement de table A. Vous pouvez également ajouter un index unique à cette colonne pour sécuriser une relation un à un
  2. Ajouter un déclencheur DB - Lors de la suppression d'un enregistrement de table A, ajoutez un déclencheur DB qui supprime l'enregistrement de table B référencé
  3. Ajouter une procédure DB - Ajoutez une procédure qui supprime un enregistrement de table A, puis l'enregistrement de table B référencé à leur tour, probablement dans une transaction. À l'avenir, supprimez uniquement les enregistrements de la table A en utilisant la procédure
  4. Ne résolvez pas le problème au niveau de la base de données - Fondamentalement identique à l'option 3, mais déplacez la logique de la procédure hors de la couche de base de données dans la logique de l'application

Il peut y avoir quelque chose dans JPA qui résout votre dilemme hors de la boîte, mais sous le capot, il fera l'une des choses ci-dessus (pas l'option 1 et probablement l'option 4)


3 commentaires

Je ne suis pas tout à fait sûr que nous nous comprenions: je veux la cascade JPA et non celle de MySQL. Ce que vous dites est correct mais ma question portait sur l'option 4 utilisant la cascade JPA.


@PierreMardon C'est assez juste, mais je suppose que toute confusion que vous rencontrez au niveau JPA est due au fait que votre cascade n'a pas vraiment de sens au niveau DB sous-jacent. Je soupçonne que votre violation se produit parce que votre cascade JPA essaie de supprimer en cascade le B associé avant de supprimer le A ciblé (car c'est ainsi que les cascades fonctionnent normalement) .. il ne peut pas supprimer le B associé en premier car il est référencé par le A ciblé . Ce dont vous avez besoin n'est pas vraiment une suppression en cascade, car vous voulez d'abord supprimer le A ciblé puis le B. associé.


Oui, j'ai clairement compris que la conception est mauvaise, mais je dois y faire face;) Quoi qu'il en soit, il y a beaucoup de solutions de contournement (il suffit de faire la cascade à la main dans le code métier pour démarrer), ma question portait sur l'amélioration de ma compréhension de JPA; )