views:

329

answers:

4

I have a many to may relationship CohortGroup and Employee. Any time I insert an Employee into the CohortGroup hibernate deletes the group from the resolution table and inserts all the members again, plus the new one. Why not just add the new one?

The annotation in the Group:

@ManyToMany(cascade = { PERSIST, MERGE, REFRESH })
@JoinTable(name="MYSITE_RES_COHORT_GROUP_STAFF",
joinColumns={@JoinColumn(name="COHORT_GROUPID")},
inverseJoinColumns={@JoinColumn(name="USERID")})
public List<Employee> getMembers(){
  return members;
}

The other side in the Employee

@ManyToMany(mappedBy="members",cascade = { PERSIST, MERGE, REFRESH } )
public List<CohortGroup> getMemberGroups(){
  return memberGroups;
}

Code snipit

Employee emp = edao.findByID(cohortId);
CohortGroup group = cgdao.findByID(Long.decode(groupId));
group.getMembers().add(emp);
cgdao.persist(group);

below is the sql reported in the log

delete from swas.MYSITE_RES_COHORT_GROUP_STAFF where COHORT_GROUPID=?
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
insert into swas.MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)

This seams really inefficient and is causing some issues. If sevral requests are made to add an employee to the group then some get over written.

Seams like equals and hashCode might be a reason for this. Below are the implementation for these methods. Any red flags?

CohortGroup

    @Override
public int hashCode() {
    final int prime = 31;
    int result = getName().hashCode();
    result = prime * result + ((emp == null) ? 0 : emp.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj) {return true;}
    if (!(obj instanceof CohortGroup)) {return false;}
    CohortGroup other = (CohortGroup) obj;
    if(!getName().equals(other.getName())){return false;}
    if (emp == null && other.getOwner() != null) {
        return false;
    } else if (!emp.equals(other.getOwner())) {
        return false;
    }
    return true;
}

Employee

       @Override
public boolean equals(Object obj) {
    if (this == obj) {return true;}
    if (obj == null) {return false;}
    if (!(obj instanceof Employee)) {return false;}
    Employee other = (Employee) obj;
    if (EMPLID == null && other.getEMPLID() != null) {
        return false;
    } else if (!EMPLID.equals(other.getEMPLID())) {
        return false;
    }
    return true;
}

   @Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((EMPLID == null) ? 0 : EMPLID.hashCode());
    return result;
}

I have added an addMember method to the CohortGroup that adds to both sides of the relationship:

    public void addMember(Employee emp){
    this.getMembers().add(emp);
    emp.getMemberGroups().add(this);

}

Continued thanks to all that are helping.

A: 

You have to define hashCode() and equals() on your CohortGroup and Employee entities. This can be done automatically by your IDE and it can either be on the primary key (sometimes not a good idea) or on a business key (preferable).

Read this article.

Bozho
Thanks for the link to the article, it helps
Mark
+2  A: 

I highly suspect that you're not overriding equals and hashCode properly. Wrongly overriding them can lead to the kind of behavior you're experiencing (as the hash key is used as keys in maps). Double check what you did with equals and hashCode.

Using your annotated entities with good equals and hashCode, this code (logically equivalent):

Session session = HibernateUtil.beginTransaction();
Employee emp = (Employee) session.load(Employee.class, 1L);
CohortGroup group = (CohortGroup) session.load(CohortGroup.class, 1L);
group.getMembers().add(emp);
emp.getMemberGroup().add(group); // set the other side too!!
session.saveOrUpdate(group);
HibernateUtil.commitTransaction();

produces the following output on my machine:

08:10:32.426 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - processing flush-time cascades
08:10:32.431 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - dirty checking collections
08:10:32.432 [main] DEBUG org.hibernate.engine.CollectionEntry - Collection dirty: [com.stackoverflow.q2649145.CohortGroup.members#1]
08:10:32.432 [main] DEBUG org.hibernate.engine.CollectionEntry - Collection dirty: [com.stackoverflow.q2649145.Employee.memberGroup#1]
08:10:32.443 [main] DEBUG org.hibernate.engine.Collections - Collection found: [com.stackoverflow.q2649145.CohortGroup.members#1], was: [com.stackoverflow.q2649145.CohortGroup.members#1] (initialized)
08:10:32.448 [main] DEBUG org.hibernate.engine.Collections - Collection found: [com.stackoverflow.q2649145.Employee.memberGroup#1], was: [com.stackoverflow.q2649145.Employee.memberGroup#1] (uninitialized)
08:10:32.460 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - Flushed: 0 insertions, 0 updates, 0 deletions to 2 objects
08:10:32.461 [main] DEBUG o.h.e.d.AbstractFlushingEventListener - Flushed: 0 (re)creations, 2 updates, 0 removals to 2 collections
08:10:32.463 [main] DEBUG org.hibernate.pretty.Printer - listing entities:
08:10:32.473 [main] DEBUG org.hibernate.pretty.Printer - com.stackoverflow.q2649145.CohortGroup{id=1, members=[com.stackoverflow.q2649145.Employee#1]}
08:10:32.474 [main] DEBUG org.hibernate.pretty.Printer - com.stackoverflow.q2649145.Employee{id=1, memberGroup=}
08:10:32.474 [main] DEBUG o.h.p.c.AbstractCollectionPersister - Inserting collection: [com.stackoverflow.q2649145.CohortGroup.members#1]
08:10:32.480 [main] DEBUG org.hibernate.jdbc.AbstractBatcher - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
08:10:32.491 [main] DEBUG org.hibernate.SQL - insert into MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
Hibernate: insert into MYSITE_RES_COHORT_GROUP_STAFF (COHORT_GROUPID, USERID) values (?, ?)
08:10:32.496 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 1
08:10:32.497 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 2
08:10:32.499 [main] DEBUG o.h.p.c.AbstractCollectionPersister - done inserting collection: 1 rows inserted

No delete before the insert!

By the way, note that you should set the link on both sides when working with bi-directional associations, like I did on the group and on the employee.

Or add defensive link management methods on your classes, for example on CohortGroup:

public void addToMembers(Employee emp) {
    this.getMembers().add(emp);
    emp.getMemberGroup().add(this);
}

public void removeFromMembers(Employee emp) {
    this.getMembers().remove(emp);
    emp.getMemberGroup().remove(this);
}
Pascal Thivent
I will double check the hash code and equals. They are implemented, I just had Eclipse create them for me. If that doesn't do the trick I'll start down your other recommendations. I'll post my results for prosperity.
Mark
I am pretty sure I have the hashcode and equals implemented correctly but the behavior remains. The first time a group has and employee added to it there is no delete. It just happens if there are already more then one employees in the group. Not sure if your test added an employee to a group that already had employees or not.
Mark
@Mark Yes, the group had 2 employees as shown in the trace
Pascal Thivent
Pascal, thanks for helping. If I want to show the hashcode and equals methods where would I post that? As an answer to this question or edit the one above?
Mark
@Mark It's better to edit your question. Use answers for answer.
Pascal Thivent
I have put a system.out in the equals and the hashCode method. Nothing is ever printed when an insert is performed. Does this mean I am looking in the wrong place for the issue?
Mark
+1  A: 

As others have suggested, it's probably a problem with hashcode or equals.

Specifically: Hibernate proxies cause problems with instaceof which you are using in your equals method. That spells bad news.

Check this out: http://community.jboss.org/wiki/ProxyVisitorPattern

z5h
Would altering the equals method to do a comparison of the class getName (this.getClass().getName().equals(other.getClass().getName()) get around the problem, or are hinting at changing to use the visitor pattern to retrieve these lists?
Mark
No altering the method won't work. Because the proxied classes have funny names. One thing you can do is add a method like so `public Class getThisClass() { return this.getClass(); }`. When you call `getClass` on the proxy you get the proxy's class. when you call `getThisClass` it delegates to your "real" class. Kind of a hack compared with visitor.
z5h
Wouldn't I have to add some checking to ensure the method existed before calling it? The reason the equals does an instanceof is to make sure it is safe to cast the passed in object. So I either dont understand how the getThisClass would work when called from the equals method or the visitor option is still the best candidate to explore.
Mark
The objects I am using are not subclasses of anything. They do implement some interfaces but they do not extend anything. If I understand what I am reading then this is probably not the cause "Very nice in simple cases with no object hierarchy. Typecasting and instanceof work perfectly on the proxy in this case since it is a direct subclass."
Mark
A: 

I have inserts acting the way I expect them to now. Thanks to Pascal and z5h, I have learned a lot. I believe I have the hashCode and equals implemented correctly. This never solved the problem for me though. Instead I have implemented an Intermediate Entity.

For what it is worth below are the mapping in my Employee, CohortGroup, and now CohortGroupMemeber classes.

Employee:

@OneToMany(mappedBy="member")
public List<CohortGroupMember> getMemberGroups(){
   return memberGroups;
}
public void setMemberGroups(List<CohortGroupMember> grps){
   memberGroups = grps;
}

CohortGroupMember

@ManyToOne
@JoinColumn(name="USERID")
public Employee getMember(){
    return emp;
}
public void setMember(Employee e){
    emp = e;
}
@ManyToOne
@JoinColumn(name="COHORT_GROUPID")
public CohortGroup getGroup(){
    return group;
}
public void setGroup(CohortGroup cg){
    group   = cg;
}

CohortGroup

@OneToMany(mappedBy="group")
public List<CohortGroupMember> getMembers(){
    return members;
}
public void setMembers(List<CohortGroupMember> emps){
    members = emps;
}

The book I followed for this is Java Persistence with Hibernate chapter 7.2.3

Mark