【问题标题】:Hibernate many-to-many remove relationHibernate 多对多删除关系
【发布时间】:2015-12-20 16:41:25
【问题描述】:

我有一个休眠多对多关系的问题:当我从我的集合中删除一个项目时,它不会在我的数据库中删除。我知道有很多类似的问题,但我没有通过阅读它们来解决我的问题。

我已经为它编写了一个 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 ... */

编辑:为事务添加 userController 实现

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

用户DAO:

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();
            }
        }
    }

}

【问题讨论】:

  • 请贴出userController.save方法的实现。另外,事务边界是什么?
  • 我添加了一些实现。该事务运行良好,因为它成功地在代码中的任何地方使用。另请注意,清除建筑物(使用 user.getBuildings().clear())也可以并清空我的多对多数据库表!只是移除奇怪地不起作用......

标签: java hibernate orm many-to-many hibernate-mapping


【解决方案1】:

CascadeType.REMOVEmany-to-many 关联没有意义,因为当在双方都设置时,它可能会触发父母和孩子之间以及返回父母之间的链删除。如果您只在父级设置它,当删除的子级仍被其他一些父级引用时,您可能会遇到问题。

引用Hibernate docs

在多对一或多对一上启用级联通常没有意义 多对多关联。事实上 @ManyToOne 和 @ManyToMany 没有 甚至提供 orphanRemoval 属性。级联通常用于 一对一和一对多关联。

【讨论】:

    【解决方案2】:

    为什么是cascade="none"

    您应该使用cascade="detached,merge,refresh,persist"(而不是删除!)来更新集合中的删除。

    【讨论】:

      【解决方案3】:

      在用户定义的buildings 关系上将cascade='none' 替换为cascade='all' 应该可以解决问题。

      由于您正在保存用户,为了同时更新数据库中的多对多,您需要级联用户对关系的更改。

      【讨论】:

        【解决方案4】:

        我担心你正在做的事情对于休眠来说并不是一个真正的好主意,即使这是你在一段关系中会做的更常见的任务之一。实现您想要的方法是使用级联,但正如 Vlad Mihalcea 所说,这最终可能会删除关系的一端或另一端,而不仅仅是关系本身。

        作为正确的回应,我会告诉你老师会说什么......你真的有n:m关系吗?你确定它本身没有实体吗? N:M 关系非常罕见,通常意味着建模错误。即使不是这种情况并且您实际上有一个 n:m,它也应该保留在模型中,永远不要忘记您正在使用 ORM 将 ACTUAL 模型链接到您的 java 模型,因此您实际上可以在 Java 中拥有一个实体每一端的1:n关系并将其存储在关系表中。

        最好的问候!

        【讨论】:

          【解决方案5】:

          更改级联属性并没有解决我的问题。我最终决定通过为中间表创建一个对象来自己处理多对多关系,并自己管理它。它有点更多的代码,但为我想要实现的目标提供了一致的行为。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-05-03
            • 1970-01-01
            • 1970-01-01
            • 2011-08-04
            • 1970-01-01
            • 1970-01-01
            • 2022-10-07
            • 2015-08-22
            相关资源
            最近更新 更多