Hibernate many-to-many relationship delete

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; } /* Getter / Setter ... */ 

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; } /* Getter / Setter ... */ 

EDIT: Add userController implementation for transaction

 @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public User save(User user) throws ServiceException { validate(user);//Validation stuffs return userDAO.update(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(); } } } } 
+6
source share
5 answers

Changing the cascade property did not fix my problem. I finally decided to handle many-to-many relationships by creating an object for the staging table and managing it myself. This is a bit more code, but provides consistent behavior for what I wanted to achieve.

0
source

CascadeType.REMOVE does not make sense for many-to-many associations , because when it is installed on both sides, it can trigger chain deletion between parents and children and back to parents. If you install it only on the parent side, you may run into problems when other child elements still refer to child elements.

To quote Hibernate docs :

It usually doesn't make sense to include a many-to-one or many-to-many cascade. In fact, @ManyToOne and @ManyToMany do not even offer the orphanRemoval attribute. Cascading is often useful for one-to-one and one-to-multiple associations.

+10
source

Why cascade="none" ?

You should use cascade="detached,merge,refresh,persist" (not delete!) Instead to update deletions in collections.

+1
source

Replacing cascade='none' with cascade='all' for buildings defined by the user should fix the problem.

Since you save the user to also update many-to-many in the database, you need to cascade the changes in relation from the user.

+1
source

I’m afraid that what you are doing is not really a good idea with hibernation, even if this is one of the most common tasks that you will do with relationships. The way to achieve what you want uses cascades, but, as Vlad Mikhalcha says, this can lead to the removal of one or the other end of the relationship, not just the relationship itself.

As the correct answer, I will tell you what the teacher would say ... Do you really have an n: m relationship? Are you sure he has no entity? The N: M relationship is very rare and usually means that the simulation is wrong. Even if it’s not, and you actually have n: m, this should remain in the model, never forget that you use ORM to associate the ACTUAL model with your Java model so that you can actually have an entity in Java with 1: n relationships at each end and save it in a relationship table.

Yours faithfully!

0
source

All Articles