Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Bi-Directional Many to Many association creates duplicates

My question is pretty similar to this one Hibernate Bi-Directional ManyToMany Updates with Second Level cache

I've class as shown below

@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Entity 
public class A{
     private int id;
     private List<B> listB;

     ...
     @Cache (usage = CacheConcurrencyStrategy.TRANSACTIONAL)
     @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity = B.class)
     @JoinTable(name = "A_B", joinColumns = { @JoinColumn(name = "a_id") }, 
        inverseJoinColumns = { @JoinColumn(name = "b_id") })
     public List<B> getListB() {
         return listB ;
     }
}

@Cache (usage = CacheConcurrencyStrategy.TRANSACTIONAL)
@Entity
public class B{
     private int id;
     private List<A> listA;

     @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity = A.class)
     @JoinTable(name = "A_B", joinColumns = { @JoinColumn(name = "b_id") }, inverseJoinColumns = { @JoinColumn(name = "a_id") })
     public List<a> getListA() {
         return listA ;
     }

     public void addToA(A a) {
         a.getListB().add(this);
     }
}

As expected in a Many to Many relation, I've been updating both sides of the bi-directional relation .

Now the problem I face is, duplicate entries popup when I try to add/update a item in the collection. The following is the code I use to persist the entity...

b.getA().clear() ;
...
...
b.getListA().add(A) ;
b.addToA(A) ;
em.saveOrUpdate(b) ;

The following are the queries fired by Hibernate which are obtained from the logs.

delete from A_B where b_id=?

insert into A_B (b_id, a_id) values (?, ?)
insert into A_B (b_id, a_id) values (?, ?)

delete from A_B where a_id=?

insert into A_B (a_id, b_id) values (?, ?)
insert into A_B (a_id, b_id) values (?, ?)
insert into A_B (a_id, b_id) values (?, ?)
insert into A_B (a_id, b_id) values (?, ?)

Where am I going wrong here ? How to get rid of the duplicates that are being inserted ? Cache is being flushed properly but the duplicate entries are the only problem !

like image 628
Venkat Avatar asked Nov 05 '12 08:11

Venkat


1 Answers

This is a classic!

The problem is that both of your mappings are owners, when one should be the owner, and one should be inverse. Because both are owners, changes to either will result in insertions to the database; with one owner and one inverse, there will only be one set of insertions.

You should be able to rewrite B::getListA as:

 @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "listB")
 public List<A> getListA()

And have everything work.

Note that only the owning side as the @JoinTable annotation. As a rule, any given bit of the database is mapped in exactly one place in a JPA application. If you ever find yourself mapping something twice, take a long hard look to see if there's a better way to do it.

Incidentally, you don't need the targetEntity attributes; a JPA provider can work that out from the generics.

like image 168
Tom Anderson Avatar answered Oct 29 '22 23:10

Tom Anderson