17

cascade=all,delete-orphan我知道删除孤立的子对象是 SO上的一个常见问题,也是 Hibernate 新手的常见问题,并且相当标准的答案是确保您对子集合有一些变化cascade=all-delete-orphan

我希望能够让 Hibernate 检测到子集合已从父对象中清空/删除,并在更新父对象时从数据库中删除子表中的行。例如:

Parent parent = session.get(...);
parent.getChildren().clear();
session.update(parent);

我当前的Parent班级映射如下所示:

<bag name="children" cascade="all-delete-orphan">
    <key column="parent_id" foreign-key="fk_parent_id"/>
    <one-to-many class="Child"/>
</bag>

这在更新附加对象时对我来说很好,但我有一个用例,我们希望能够获取一个分离的对象(远程客户端通过 HTTP/JSON 将其发送到我们的 API 方法),并将其直接传递给 Hibernate Session - 以允许客户端能够以他们喜欢的任何方式操作父对象并保持更改。

当调用session.update(parent)我的分离对象时,子表中的行是孤立的(FK 列设置为空)但没有被删除。请注意,当我调用时session.update(),这是 Hibernate Session 第一次看到这个对象实例——我没有以任何其他方式重新附加或合并对象与 Session。我依靠客户端传递标识符对应于数据库中实际对象的对象。例如,我的 API 服务方法中的逻辑是这样的:

String jsonString = request.getParameter(...);
Parent parent = deserialize(jsonString);
session.update(parent);

当传递给时,Hibernate 是否可以检测分离的父对象中的孤儿集合session.update(parent)?还是我以某种方式误用了分离的对象?

我希望我可以避免与 Hibernate 进行任何类型的复杂交互,以将更改持久保存到分离的实例。我的 API 方法在调用 之后不需要进一步修改分离的对象session.update(parent),该方法只负责持久化远程客户端应用程序所做的更改。

4

2 回答 2

8

您的映射(简化)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="br.com._3988215.model.domain">
    <class name="Parent" table="PARENT">
        <id name="id">
            <generator class="native"/>
        </id>
        <bag cascade="all,delete-orphan" name="childList">
            <key column="PARENT_ID" not-null="false"/>
            <one-to-many class="Child"/>
        </bag>
    </class>
    <class name="Child" table="CHILD">
        <id name="id" column="CHILD_ID">
            <generator class="native"/>
        </id>
    </class>
</hibernate-mapping>

生产

PARENT
    ID

CHILD
    CHILD_ID
    PARENT_ID

按照你说的

我希望能够让 Hibernate检测到子集合已从父对象中删除,并在父对象更新时从数据库中删除子表中的行

就像是

Parent parent = session.get(...);
parent.getChildren().clear();

session.update(parent);

你说它工作正常,因为你有一个附加的 Parent 实例

现在让我们看下面的(注意Assert.assertNull(second)

public class WhatYouWantTest {

    private static SessionFactory sessionFactory;

    private Serializable parentId;

    private Serializable firstId;
    private Serializable secondId;

    @BeforeClass
    public static void setUpClass() {
        Configuration c = new Configuration();
        c.addResource("mapping.hbm.3988215.xml");

        sessionFactory = c.configure().buildSessionFactory();
    }

    @Before
    public void setUp() throws Exception {
        Parent parent = new Parent();
        Child first   = new Child();
        Child second  = new Child();

        Session session = sessionFactory.openSession();
        session.beginTransaction();

        parentId = session.save(parent);
        firstId  = session.save(first);
        secondId = session.save(second);

        parent.getChildList().add(first);
        parent.getChildList().add(second);

        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void removed_second_from_parent_remove_second_from_database() {
        Parent parent = new Parent();
        parent.setId((Integer) parentId);

        Child first = new Child();
        first.setId((Integer) firstId);

        /**
          * It simulates the second one has been removed
          */
        parent.getChildList().add(first);

        Session session = sessionFactory.openSession();
        session.beginTransaction();

        session.update(parent);

        session.getTransaction().commit();
        session.close();

        session = sessionFactory.openSession();
        session.beginTransaction();

        Child second = (Child) session.get(Child.class, secondId);
        Assert.assertNull(second);

        session.getTransaction().commit();
        session.close();
    }
}

不幸的是,测试没有通过。你可以做什么 ???

  • 启用长时间的对话

休眠参考说

扩展(或长)会话 - 在提交数据库事务后,Hibernate 会话可能会与底层 JDBC 连接断开连接,并在出现新的客户端请求时重新连接。这种模式被称为会话每次会话,甚至不需要重新连接。自动版本控制用于隔离并发修改,通常不允许自动刷新会话,而是明确地刷新。

免责声明:我没有任何使用长时间对话的场景。Java EE 有状态会话 bean 支持长时间运行的会话。但它支持 JPA(不是 Hibernate)

或者,您可以创建一个替代映射,使您的孩子成为复合元素。因为它的生命周期依赖于父对象,所以你可以依赖复合元素来得到你想要的

创建一个名为 AlternativeParent 的类,它扩展了 Parent

public class AlternativeParent extends Parent {}

现在它的映射(注意子作为复合元素而不是普通的@Entity)

<class name="AlternativeParent" table="PARENT">
    <id name="id">
        <generator class="native"/>
    </id>
    <bag name="childList" table="CHILD">
        <key column="PARENT_ID" not-null="false"/>
        <composite-element class="Child">
            <property column="CHILD_ID" name="id"/>
        </composite-element>
    </bag>
</class>

现在在 Child 类中实现一个方便的 equals 方法

public boolean equals(Object o) {
    if (!(o instanceof Child))
        return false;

    Child other = (Child) o;
    // identity equality
    // Used by composite elements
    if(getId() != null) {
        return new EqualsBuilder()
                   .append(getId(), other.getId())
                   .isEquals();
    } else {
        // object equality
     }
}

如果我重构上面显示的测试用例(现在改用 AlternativeParent)

@Test
public void removed_second_from_parent_remove_second_from_database() {
    AlternativeParent parent = new AlternativeParent();
    parent.setId((Integer) parentId);

    Child first = new Child();
    first.setId((Integer) firstId);

    /**
      * It simulates the second one has been removed
      */
    parent.getChildList().add(first);

    Session session = sessionFactory.openSession();
    session.beginTransaction();

    session.update(parent);

    session.getTransaction().commit();
    session.close();

    session = sessionFactory.openSession();
    session.beginTransaction();

    Child second = (Child) session.get(Child.class, secondId);
    Assert.assertNull(second);

    session.getTransaction().commit();
    session.close();

}

我看到一个绿色的酒吧

于 2010-10-30T19:54:57.200 回答
1

我认为,在使用分离会话时,您可能会遇到收藏问题。我会建议您首先加载带有集合的实体,然后使用更改更新该实体,这将有所帮助。

于 2010-10-27T04:52:56.297 回答