休眠是否可以在更新分离的对象时删除孤立集合?

2022-09-03 02:47:51

我知道删除孤立的子对象是 SO 上的一个常见问题,也是刚接触 Hibernate 的人的常见问题,而且相当标准的答案是确保你有子集合的一些变体。cascade=all,delete-orphancascade=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会话 - 允许客户端能够以他们喜欢的任何方式操作父对象并保留更改。

调用分离的对象时,子表中的行是孤立的(FK 列设置为 null),但不会被删除。请注意,当我调用 时,这是休眠会话第一次看到此对象实例 - 我不会以任何其他方式将对象与会话重新附加或合并。我依靠客户端来传递其标识符与数据库中的实际对象相对应的对象。例如,我的 API 服务方法中的逻辑如下所示:session.update(parent)session.update()

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

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

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


答案 1

您的映射(简化)

<?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);

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

现在让我们看看下面的一个(注意 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();
    }
}

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

  • 启用长时间运行的对话

休眠引用说

扩展(或长)会话 - 在提交数据库事务后,休眠会话可能会与底层 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>

现在在子类中实现一个方便的等式方法

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
     }
}

如果我重构上面显示的测试用例(现在使用替代父母代替)

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

}

我看到一个绿色条


答案 2

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


推荐