views:

69

answers:

2

I have to test some code I have not myself written. It is an integration test: the application is running continuously on a server and my tests run against it.

The tests are Selenium tests, they launch a browser, execute some JavaScript inside it to simulate user actions and checks if the database is correctly updated. I have to restore the database to its initial state after each.

To do this, I use Spring annotations and Hibernate via DAO's I have not myself written.

The problem is that there are circular foreign keys. An object of class A has a OneToMany relationship with objects of type B, and there is also a ManyToOne association with the same class. I try to delete an object of type A and all its associated B's in the same transaction, but it doesn't work because Hibernate tries to set "defaultB" to null before deleting the object of type A. It is completely unnecessary to nullify it, although it makes sense to do it once the referred object of type B is deleted.

I (naively) thought that because the 2 operations were executed in the same transaction, deleting the object "a" of type A referring to (and referenced by) the object "b" of class B and deleting b at the same time would be no problem. However, I was plain wrong. It there is way to do this without changing the DB model (which I haven't written)?

Update 1: I don't understand why, when I execute mySession.delete(B), Hibernate tries to nullify a key it knows as non-nullable...any thoughts about this?

Update 2: there is a one-to-many relationship from class C to class B. Hibernate also tries to nullify the "c_id" field in the table corresponding to B, when I delete the C object that has this c_id. And that even though I delete the object of class B before its "parent". I know Hibernate reorders the queries and adds some stuff of its own, but I don't get the point of reordering queries that are already in the correct order to make them fail.

Here are (relevant parts of) the 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;
    }

}
A: 

I assume that you want to delete a, but Hibernate does not allow it because b still refer to it?

Since you meta-model do not specify cascade delete, you need to "break" the link of b to a before deleting a. So do b.setA(null) before deleting a.

Thierry-Dimitri Roy
It is not-nullable, so I don't think it would work.
Pierre Gardin
+2  A: 

I try to delete an object of type A and all its associated B's in the same transaction

You should cascade the REMOVE operation for this if you don't want to have to remove all Bs manually. I would try the following (using cascade on both 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;
    }
}

I cannot change these annotations. BTW, I do remove all associated B's manually, it's just that the queries Hibernate issues don't do what I want.

Ok... But then my guess is that you're not updating correctly both sides of the bidirectional association before remove the entities. This is typically done in defensive programming methods like this (in A):

public removeFromBs(B b) {
    b.setA(null);
    this.getBs().remove(b);
}
Pascal Thivent
I cannot change these annotations. BTW, I do remove all associated B's manually, it's just that the queries Hibernate issues don't do what I want.
Pierre Gardin
Why would I use b.setA(null);when 'a' is non-nullable?
Pierre Gardin