Hibernate многие-ко-многим удалить отношение
у меня проблема с отношением hibernate many-to-many: когда я удаляю один элемент из своего набора, он не удаляется в моей базе данных. Я знаю, что есть тонны подобных проблем,но мне не удалось исправить мои, прочитав их.
я написал для него тестовый случай JUnit. Моя связь между зданиями и пользователями:
@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());
}
вот ошибка, которую я получил:
...
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]
когда я комментирую "тест 1" и раскомментирую строки "Тест 2" , я иду следующая ошибка:
junit.framework.AssertionFailedError: 
Expected :1
Actual   :2
вот мой hbm.классы xml:
<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>
и
<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>
и классы
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 ... */
и
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: добавить реализацию userController, для транзакции
@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 {
}
и 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();
            }
        }
    }
}
            5 ответов
на CascadeType.REMOVE не имеет смысла для многих-ко-многим объединений потому что, когда он установлен с обеих сторон, он может вызвать удаление цепочки между родителями и детьми и обратно к родителям. Если вы установите его только на родительской стороне, вы можете столкнуться с проблемами, когда удаляющий ребенок все еще ссылается на некоторых других родителей.
цитировать Hibernate docs:
обычно нет смысла включать каскад на много-к-одному или ассоциация "многие ко многим". На самом деле @ManyToOne и @ManyToMany не даже предлагают атрибут orphanRemoval. Каскадирование часто полезно для один-к-одному и один-ко-многим объединений.
почему cascade="none"?
вы должны использовать cascade="detached,merge,refresh,persist" (не удалить !) вместо обновления удалений в коллекциях.
замена cascade='none' by cascade='all' на buildings определена связь пользователя должно решить проблему.
поскольку вы сохраняете пользователя, чтобы также обновить многие ко многим в БД, вам нужно каскадировать изменения в отношениях от пользователя.
Я боюсь, что то, что вы делаете, на самом деле не очень хорошая идея с hibernate, даже если это одна из самых обычных задач, которые вы бы сделали с отношениями. Способ достичь желаемого-использовать каскады, но, как говорит Влад Михальча, это может привести к удалению одного или другого конца отношений, а не только самих отношений.
в качестве правильного ответа я бы сказал вам, что сказал бы учитель... У вас действительно есть n: m отношения? Вы уверены, что у него нет сущность сама по себе? N: M отношения очень редко можно найти и обычно означает, что моделирование неверно. Даже если это не так, и у вас на самом деле есть n:m, это должно оставаться в модели, никогда не забывайте, что вы используете ORM для связи фактической модели с вашей моделью java, чтобы вы могли фактически иметь сущность в Java с отношениями 1:n на каждом конце и хранить ее в таблице отношений.
с наилучшими пожеланиями!
изменение свойства cascade не устранило мою проблему. В конце концов я решаю сам разобраться с отношениями "многие ко многим", создав объект для промежуточной таблицы и управляя им самостоятельно. Это немного больше кода, но обеспечивает последовательное поведение для того, что я хотел достичь.