简体   繁体   English

Hibernate 多对多删除关系

[英]Hibernate many-to-many remove relation

I have an issue with an hibernate many-to-many relation: when I remove one item from my set, it is not removed in my database.我有一个休眠多对多关系的问题:当我从我的集合中删除一个项目时,它并没有从我的数据库中删除。 I know there are tons of similar issues, but I did not succeed in fixing mine by reading them.我知道有很多类似的问题,但我没有通过阅读来成功解决我的问题。

I have written a JUnit test case for it.我已经为它编写了一个 JUnit 测试用例。 My association is 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 the error I got:这是我得到的错误:

...
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 the "Test 1" and uncomment the "Test 2" lines, I go the following error:当我评论“测试 1”并取消注释“测试 2”行时,出现以下错误:

junit.framework.AssertionFailedError: 
Expected :1
Actual   :2

Here are my hbm.xml classes:这是我的 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>

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 the 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 the transaction编辑:为事务添加 userController 实现

@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public User save(User user) throws ServiceException {
    validate(user);//Validation stuffs
    return userDAO.update(user);
}

The userDAO:用户道:

public class UserDAOImpl extends HibernateDAOImpl<User> implements UserDAO {
}

And the HibernateDAOImpl:和 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();
            }
        }
    }

}

The CascadeType.REMOVE doesn't make sense for many-to-many associations because when set on both sides it could trigger a chain deletion between parents and children and back to parents. CascadeType.REMOVE对于many-to-many关联没有意义,因为在两侧设置时,它可能会触发父子之间的链删除并返回父子。 If you only set it on the parent side, you could bump into issues when a deleting child is still referenced by some other parents.如果你只在父端设置它,当删除的孩子仍然被其他一些父母引用时,你可能会遇到问题。

To quote the Hibernate docs :引用Hibernate 文档

It does not usually make sense to enable cascade on a many-to-one or many-to-many association.在多对一或多对多关联上启用级联通常没有意义。 In fact the @ManyToOne and @ManyToMany don't even offer a orphanRemoval attribute.事实上,@ManyToOne 和@ManyToMany 甚至不提供 orphanRemoval 属性。 Cascading is often useful for one-to-one and one-to-many associations.级联通常对一对一和一对多关联很有用。

Why cascade="none" ?为什么cascade="none"

You should use cascade="detached,merge,refresh,persist" (not delete.) instead to update removals in collections.您应该使用cascade="detached,merge,refresh,persist" (不是删除。)来更新集合中的删除。

Replacing cascade='none' by cascade='all' on the buildings relationship defined on the user should fix the problem.在用户定义的buildings关系上用cascade='all'替换cascade='none'应该可以解决问题。

Since you are saving the user, in order to also update the many-to-many in the DB, you need to cascade the changes on the relationship from the user.由于您正在保存用户,为了也更新数据库中的多对多,您需要级联用户关系的更改。

I am afraid that what you are doing is not really a good idea with hibernate even if it is one of the more usual task you would do with a relationship.恐怕你正在做的事情对于休眠来说并不是一个好主意,即使这是你对一段关系所做的更常见的任务之一。 The way to achieve what you want is using cascades but as Vlad Mihalcea says, this can end up deleting one or the other end of the relationship and not only the relationship itself.实现你想要的方法是使用级联,但正如 Vlad Mihalcea 所说,这最终可能会删除关系的一端或另一端,而不仅仅是关系本身。

As a proper response I would tell you what a teacher would say... Do you really have an:m relationship?作为一个适当的回应,我会告诉你老师会说什么......你真的有一个:m关系吗? Are you sure it doesn't have entity by itself?你确定它本身没有实体吗? N:M relationships are very rare to find and usually means that the modeling is wrong. N:M 关系很难找到,通常意味着建模是错误的。 Even when this is not the case and you actually have an n:m, this should stay in the model, never forget that you are using an ORM to link the ACTUAL model to your java model so you can actually have an entity in Java with 1:n relationships on each end and store it in the relationship table.即使情况并非如此,并且您实际上有一个 n:m,它也应该保留在模型中,永远不要忘记您正在使用 ORM 将 ACTUAL 模型链接到您的 Java 模型,这样您实际上可以在 Java 中拥有一个实体每一端的 1:n 关系并将其存储在关系表中。

Best regards!最好的祝福!

Changing the cascade property has not fixed my issue.更改级联属性并没有解决我的问题。 I finally decide to handle the many-to-many relationship myself by creating an object for the intermediate table, and manage it by my own.我最终决定通过为中间表创建一个对象来自己处理多对多关系,并自行管理它。 It is a bit more of code, but provides a consistent behavior for what I wanted to achieve.它的代码有点多,但为我想要实现的目标提供了一致的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM