I have a problem with a many-to-many sleeping relationship: when I delete one item from my collection, it is not deleted in my database. I know that there are many similar problems, but I was not able to fix mine by reading them.
I wrote a JUnit test case for it. My connection between Buildings and Users:
@Test public void testBuildingManyToMany(){ //Create 2 buildings Building building = createBuilding("b1"); Building building2 = createBuilding("b2"); //Create 1 user User user = createUser("u1"); //Associate the 2 buildings to that user user.getBuildings().add(building); building.getUsers().add(user); user.getBuildings().add(building2); building2.getUsers().add(user); userController.save(user); user = userController.retrieve(user.getId()); Assert.assertEquals(2, user.getBuildings().size());//Test OK //Test 1: remove 1 building from the list user.getBuildings().remove(building); building.getUsers().remove(user); userController.save(user); //Test 2: clear and add //user.getBuildings().clear(); //user.getBuildings().add(building); //userController.save(user); //user = userController.retrieve(user.getId()); //Assert.assertEquals(1, user.getBuildings().size()); }
Here is my mistake:
... Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?) Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?) Hibernate: delete from building_useraccount where userid=? and buildingid=? Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?) 4113 [main] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 23505, SQLState: 23505 4113 [main] ERROR org.hibernate.util.JDBCExceptionReporter - Unique index or primary key violation: "PRIMARY_KEY_23 ON PUBLIC.BUILDING_USERACCOUNT(BUILDINGID, USERID) VALUES ( /* key:0 */ 201, 201)"; SQL statement: insert into building_useraccount (userid, buildingid) values (?, ?) [23505-176]
When I comment on "Test 1" and uncomment the lines of "Test 2", I make the following error:
junit.framework.AssertionFailedError: Expected :1 Actual :2
Here are my hbm.xml classes:
<hibernate-mapping default-lazy="true"> <class name="my.model.pojo.Building" table="building"> <cache usage="read-write" /> <id name="id" column="id" type="java.lang.Long"> <generator class="sequence"> <param name="sequence">building_id_sequence</param> </generator> </id> <property name="name" type="java.lang.String" column="name" not-null="true" /> ... <set name="users" cascade="none" lazy="true" inverse="true" table="building_useraccount"> <key column="buildingid" /> <many-to-many class="my.model.pojo.User" column="userid" /> </set> </class> </hibernate-mapping>
and
<hibernate-mapping default-lazy="true"> <class name="my.model.pojo.User" table="useraccount"> <cache usage="read-write" /> <id name="id" column="id" type="java.lang.Long"> <generator class="sequence"> <param name="sequence">useraccount_id_sequence</param> </generator> </id> <property name="login" type="java.lang.String" column="login" not-null="true" unique="true" length="40" /> ... <set name="buildings" cascade="none" lazy="false" fetch="join" table="building_useraccount"> <key column="userid" /> <many-to-many class="my.model.pojo.Building" column="buildingid" /> </set> </class> </hibernate-mapping>
and classes
public class User implements Serializable, Identifiable { private static final long serialVersionUID = 1L; private int hashCode; private Long id; private String login; private Set<Building> buildings = new HashSet<Building>(); public boolean equals(Object value) { if (value == this) return true; if (value == null || !(value instanceof User)) return false; if (getId() != null && getId().equals(((User) value).getId())) return true; return super.equals(value); } public int hashCode() { if (hashCode == 0) { hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode(); } return hashCode; }
and
public class BuildingBase implements Serializable, Identifiable { private static final long serialVersionUID = 1L; private int hashCode; private Long id; private String name; private Set<User> users = new HashSet<User>(); public boolean equals(Object value) { if (value == this) return true; if (value == null || !(value instanceof Building)) return false; if (getId() != null && getId().equals(((Building) value).getId())) return true; return super.equals(value); } public int hashCode() { if (hashCode == 0) { hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode(); } return hashCode; }
EDIT: Add userController implementation for transaction
@Transactional(readOnly = false, propagation = Propagation.REQUIRED) public User save(User user) throws ServiceException { validate(user);
userDAO:
public class UserDAOImpl extends HibernateDAOImpl<User> implements UserDAO { }
And HibernateDAOImpl:
public class HibernateDAOImpl<T> implements DAO<T> { public T update(T entity) { return executeAndCreateSessionIfNeeded(new HibernateAction<T>() { @Override public T execute(Session session) { return (T) session.merge(entity); } }); } protected <E> E executeAndCreateSessionIfNeeded(HibernateAction<E> action) { Session session = null; try { session = sessionFactory.getCurrentSession(); return executeAction(action, session); } finally { if (session != null) { session.close(); } } } }