2010-08-18 3 views
3

Je dois tester un code que je n'ai pas écrit moi-même. C'est un test d'intégration: l'application s'exécute en continu sur un serveur et mes tests s'exécutent contre elle.Clé étrangère circulaire hibernate

Les tests sont des tests Selenium, ils lancent un navigateur, exécutent du code JavaScript à l'intérieur pour simuler les actions de l'utilisateur et vérifie si la base de données est correctement mise à jour. Je dois restaurer la base de données à son état initial après chacun. Pour ce faire, j'utilise les annotations Spring et Hibernate via DAO que je n'ai pas écrit moi-même.

Le problème est qu'il existe des clés étrangères circulaires. Un objet de classe A a une relation OneToMany avec des objets de type B, et il existe également une association ManyToOne avec la même classe. J'essaie de supprimer un objet de type A et tous ses B associés dans la même transaction, mais cela ne fonctionne pas car Hibernate tente de définir "defaultB" sur null avant de supprimer l'objet de type A. Il est totalement inutile de l'annuler, bien qu'il soit logique de le faire une fois que l'objet référencé de type B est supprimé. J'ai (naïvement) pensé que parce que les 2 opérations étaient exécutées dans la même transaction, en supprimant l'objet "a" de type A en référence à (et référencé par) l'objet "b" de la classe B et en supprimant b à la en même temps ne serait pas un problème. Cependant, je me trompais complètement. Il y a moyen de le faire sans changer le modèle DB (que je n'ai pas écrit)?

Mise à jour 1: Je ne comprends pas pourquoi, quand j'exécute mySession.delete (B), Hibernate essaie d'annuler une clé qu'il sait non-nullable ... des pensées à ce sujet?

Mise à jour 2: il y a un-à-plusieurs de la classe C à la classe B. Hibernate essaie également d'annuler le champ « C_Id » dans le tableau correspondant à B, lorsque je supprime l'objet C qui a ce c_id. Et cela même si je supprime l'objet de la classe B avant son "parent". Je sais qu'Hibernate réorganise les requêtes et ajoute des choses qui lui sont propres, mais je n'obtiens pas le point de réorganiser les requêtes qui sont déjà dans le bon ordre pour les faire échouer.

Voici (parties pertinentes) les classes:

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

    private Set<B> bs; 
    private B defaultB; 

    @OneToMany(mappedBy = "a", fetch = LAZY) 
    public Set<B> getBs() { 
     return bs; 
    } 

    public void setBs(Set<B> bs) { 
     this.bs = bs; 
    } 

    @ManyToOne(fetch = LAZY, optional = false) 
    @JoinColumn(name = "default_b_id", nullable = false) 
    public B getDefaultB(){ 
     return defaultB; 
    } 

    public void setDefaultB(B defaultB) { 
     this.defaultB = defaultB; 
    } 
} 

@Entity 
@Table(name = "B") 
public class B { 

    private a; 

    @ManyToOne(fetch = LAZY, optional = false) 
    @JoinColumn(name = "A_id", nullable = false) 
    public A getA() { 
     return a; 
    } 

    public void setA(A a) { 
     this.a = a; 
    } 

} 
+2

Je ne suis pas très familier avec JPA annoations mais ne vous voulez définir une sorte de 'option cascade' sur la' @ManyToOne 'relations? –

+0

Hibernate ne réorganise pas les requêtes pour les faire échouer. Il les réorganise en fonction des associations et de ce que vous avez fait avec elles pour que les requêtes aboutissent. Si la commande est fausse, les chances sont que vous avez fait quelque chose. De toute façon, 'defaultB' est-il membre du' Bs'? Pouvez-vous montrer votre logique de suppression? –

Répondre

0

Je suppose que vous voulez supprimer un, mais Hibernate ne le permet pas parce que b toujours s'y référer?

Puisque votre méta-modèle ne spécifie pas de suppression en cascade, vous devez "casser" le lien de b en a avant de supprimer a. Alors faites b.setA(null) avant de supprimer a.

+0

Il est non-nullable, donc je ne pense pas que cela fonctionnerait. –

2

je tente de supprimer un objet de type A et tous ses est associé de B dans la même transaction

Vous devriez en cascade l'opération REMOVE pour cela si vous ne voulez pas avoir à supprimer tous les hôtes manuellement. Je les opérations suivantes (en utilisant cascade des deux associations):

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

    private Set<B> bs; 
    private B defaultB; 

    @OneToMany(mappedBy = "a", fetch = LAZY, cascade=CascadeType.REMOVE) 
    public Set<B> getBs() { 
     return bs; 
    } 

    public void setBs(Set<B> bs) { 
     this.bs = bs; 
    } 

    @ManyToOne(fetch = LAZY, optional = false, cascade=CascadeType.REMOVE) 
    @JoinColumn(name = "default_b_id", nullable = false) 
    public Strategy getDefaultB(){ 
     return defaultB; 
    } 

    public void setDefaultB(B defaultB) { 
     this.defaultB = defaultB; 
    } 
} 

Je ne peux pas changer ces annotations. BTW, je supprime tous les B associés manuellement, c'est juste que les questions Hibernate questions ne font pas ce que je veux.

Ok ... Mais je suppose que vous n'êtes pas mise à jour correctement les deux côtés de l'association bidirectionnelle avant les entités supprimer. Cela se fait généralement dans les méthodes de programmation défensives comme celui-ci (en A):

public removeFromBs(B b) { 
    b.setA(null); 
    this.getBs().remove(b); 
} 
+0

Je ne peux pas modifier ces annotations. BTW, je supprime tous les B associés manuellement, c'est juste que les questions Hibernate questions ne font pas ce que je veux. –

+0

Pourquoi devrais-je utiliser b.setA (null); lorsque 'a' est non nullable? –