-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 
Author Message
 Post subject: Removal from many-many set fails -- problem with equals()
PostPosted: Tue Sep 01, 2009 6:15 pm 
Newbie

Joined: Tue Sep 01, 2009 5:33 pm
Posts: 3
Hi,

I'm just getting started with Hibernate -- apologies in advance.

I have a many-many relationship of Users and Groups. The crux of the issue is this:

The following code has the expected result of removing the association from both sides:

Code:
myUser.getGroups().remove(myGroup);
myGroup.getMembers().remove(myUser);


However, when I try to encapsulate this into a convenience method in the User class, as follows:

Code:
public void removeGroup(Group group) {
  this.groups.remove(group);
  group.members.remove(this);  // DOES NOT WORK
}


it fails to remove the user from the group. Debugging of the code reveals that something suspect is happening in the User.equals() method. The object passed in to equals() is a javassist proxy class, and it appears to be un-initialized. So when the equals() implementation compares the objects (in this case, comparing the username values), they are not equal, which ultimately seems to cause the group.getMembers().remove() to fail because the (proxy) user object does not appear to be a member of the set.

Is this behavior as expected, or am I doing something wrong?

I have not done much testing with various different Lazy Load and Fetch annotations, though it seems odd to me that these would affect the fundamental programming semantics that I'm seeing here.

Thanks in advance,

Jon

The full source code in question and the unit tests are copied below:

User class:
Code:
package org.mitre.hibernatetest.entity;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.apache.log4j.Logger;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.annotations.Parameter;

@Entity
// "User" is a SQL reserved word, so call the table something else
@Table(name = "users")
public class User implements java.io.Serializable {
    private static final long serialVersionUID = 5429145115928312017L;
    private static Logger logger = Logger.getLogger(Group.class);

    @Id
    @GeneratedValue(generator = "tableGenerator")
    @GenericGenerator(name = "tableGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator",
        parameters = {@Parameter(name = "segment_value", value = "user_seq")})
    private Long id;
    @Column(nullable = false, unique = true)
    private String username;
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(name = "group_members", joinColumns=@JoinColumn(name="user_id", nullable = false),
    inverseJoinColumns=@JoinColumn(name="group_id", nullable = false))
    private Set<Group> groups = new HashSet<Group>();

    public Set<Group> getGroups() {
        return groups;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

   
    public Long getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {

        if (obj == null) {
            return false;
        }

        if (!(obj instanceof User)) {
            return false;
        }

        final User other = (User) obj;
        logger.info("this: " + this.username + " (" + this.getClass() + ")");
        logger.info("other: " + other.username + " (" + other.getClass() + ")");

        if ((this.username == null) ? (other.username != null) : !this.username.equalsIgnoreCase(other.username)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 41 * hash + (this.username != null ? this.username.hashCode() : 0);
        return hash;
    }

    public boolean addGroup(Group group) {
        boolean added = this.groups.add(group);
        if (added) {
            group.addMember(this);
        }
        return added;
    }

    public boolean removeGroup(Group group) {
        boolean removed = this.groups.remove(group);
        if (removed) {
            group.removeMember(this);
        }
        return removed;
    }
}


Group class:
Code:
package org.mitre.hibernatetest.entity;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

import org.apache.log4j.Logger;

@Entity
@Table(name = "groups")
public class Group implements Serializable {
    private static final long serialVersionUID = -4453002379222933845L;

    private static Logger logger = Logger.getLogger(Group.class);

    @Id
    @GeneratedValue(generator = "tableGenerator")
    @GenericGenerator(name = "tableGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator",
        parameters = {@Parameter(name = "segment_value", value = "group_seq")})
    private Long id;
    //
    @Column(name = "group_name", nullable = false)
    private String groupName;
    //
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},
    mappedBy = "groups",
    targetEntity = User.class)
    private Set<User> members = new HashSet<User>();

    public Group() {
    }

    public Group(String groupName) {
        this.groupName = groupName;
    }

    public Set<User> getMembers() {
        // return Collections.unmodifiableSet(members);
        return members;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public Long getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
       
        if (!(obj instanceof Group)) {
            return false;
        }
        final Group other = (Group) obj;

        logger.info("this group: " + this.groupName);
        logger.info("other group: " + other.groupName);

        if ((this.groupName == null) ? (other.groupName != null) : !this.groupName.equalsIgnoreCase(other.groupName)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 23 * hash + (this.groupName != null ? this.groupName.hashCode() : 0);
        return hash;
    }

    protected boolean addMember(User member) {
        return members.add(member);
    }

    protected boolean removeMember(User member) {
        boolean removed = members.remove(member);
        logger.info("Removed member: " + removed);
        return removed;
    }
}


Test class (using Spring):
Code:
package org.mitre.hibernatetest;

import javax.annotation.Resource;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.junit.Test;

import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import org.junit.After;
import org.junit.Before;
import org.mitre.hibernatetest.entity.Group;
import org.mitre.hibernatetest.entity.User;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/applicationContext.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
@Transactional
public class TestDomainObjects extends AbstractTransactionalJUnit4SpringContextTests {

    @Resource
    private SessionFactory sessionFactory;
   
    private User bart;
    private User lisa;
    private Group users;
    private Group admins;

    private Long bartId;
    private Long lisaId;
    private Long usersId;
    private Long adminsId;

    @Before
    public void loadData() {

        Session sess = sessionFactory.getCurrentSession();
        Transaction tx = sess.beginTransaction();

        bart = new User();
        bart.setUsername("Bart");
        bartId = (Long) sess.save(bart);

        lisa = new User();
        lisa.setUsername("Lisa");
        lisaId = (Long) sess.save(lisa);

        users = new Group("USERS");
        usersId = (Long) sess.save(users);
        admins = new Group("ADMINS");
        adminsId = (Long) sess.save(admins);

        bart.addGroup(users);
        bart.addGroup(admins);
        lisa.addGroup(users);

        sess.flush();
        sess.clear();

        bart = (User) sess.load(User.class, bartId);
        lisa = (User) sess.load(User.class, lisaId);

        users = (Group) sess.load(Group.class, usersId);
        admins = (Group) sess.load(Group.class, adminsId);

    }

    @Test
    public void testRemoval1() throws Exception {
        bart.getGroups().remove(users);
        users.getMembers().remove(bart);
        assertFalse(bart.getGroups().contains(users));
        assertFalse(users.getMembers().contains(bart));
    }

    @Test
    public void testRemoval2() throws Exception {
        bart.removeGroup(users);
        assertFalse(bart.getGroups().contains(users));
        assertFalse(users.getMembers().contains(bart)); // FAILS!!!
    }

    @After
    public void cleanup() {

        Session sess = sessionFactory.getCurrentSession();
        sess.delete(bart);
        sess.delete(lisa);
        sess.delete(users);
        sess.delete(admins);
        sess.flush();
        sess.clear();
    }
}


Log output from the User.equals() method:

Code:
2009-09-01 17:28:24,667 INFO org.mitre.hibernatetest.entity.Group - this: Bart (class org.mitre.hibernatetest.entity.User)
2009-09-01 17:28:24,667 INFO org.mitre.hibernatetest.entity.Group - other: null (class org.mitre.hibernatetest.entity.User_$$_javassist_4)
2009-09-01 17:28:24,667 INFO org.mitre.hibernatetest.entity.Group - Removed member: false


Top
 Profile  
 
 Post subject: Re: Removal from many-many set fails -- problem with equals()
PostPosted: Wed Sep 02, 2009 10:35 am 
Newbie

Joined: Tue Sep 01, 2009 5:33 pm
Posts: 3
Problem solved. As it says in the FAQ:

Quote:
It is actually surprisingly hard to get your implementation of equals() right when proxies are enabled. You must be prepared for the possibility that that a class instance is really a proxy for that class, so you can't access instance variables directly (use the accessors).


The problem was solved by using accessors rather than fields in my equals() method.


Top
 Profile  
 
 Post subject: Re: Removal from many-many set fails -- problem with equals()
PostPosted: Wed Sep 02, 2009 6:18 pm 
Expert
Expert

Joined: Tue May 13, 2008 3:42 pm
Posts: 919
Location: Toronto & Ajax Ontario www.hibernatemadeeasy.com
Indeed, using the accessor when the proxy is live will help you avoid these types of problems. Thanks for the postback!

_________________
Cameron McKenzie - Author of "Hibernate Made Easy" and "What is WebSphere?"
http://www.TheBookOnHibernate.com Check out my 'easy to follow' Hibernate & JPA Tutorials


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.