更新分离对象时,Hibernate是否可以删除孤立的集合? [英] Can Hibernate delete orphaned collections when updating a detached object?

查看:93
本文介绍了更新分离对象时,Hibernate是否可以删除孤立的集合?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道删除孤儿对象是SO上的一个常见问题,也是新接触Hibernate的人的一个常见问题,并且相当标准的答案是确保您有一些 cascade = all的变体,删除孤儿级联=全部删除 - 孤儿



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

 父亲= session.get(...); 
parent.getChildren()。clear();
session.update(parent);

我目前对 Parent 类的映射如:

 < bag name =childrencascade =all-delete-orphan> 
< key column =parent_idforeign-key =fk_parent_id/>
<一对多班=小孩/>
< / bag>

在更新附加对象时,这对我很好,但我有一个用例, d喜欢能够接受一个分离的对象(已经通过HTTP / JSON由远程客户端发送到我们的API方法),并将其直接传递给Hibernate Session - 以允许客户端能够操纵父对象



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

  String jsonString = request.getParameter(...) ; 
父级父母=反序列化(jsonString);
session.update(parent);

传递给<$ c $时,Hibernate是否可以检测到已分离父对象中的孤儿子集合C>了session.update(亲本)?或者我以某种方式错误地使用分离的对象?



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

解决方案

您的映射(简体)

 <?xml version =1.0encoding =UTF-8?> 
<!DOCTYPE hibernate-mapping PUBLIC - // Hibernate / Hibernate Mapping DTD 3.0 // ENhttp://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">
< hibernate-mapping package =br.com._3988215.model.domain>
< class name =Parenttable =PARENT>
< id name =id>
< generator class =native/>
< / id>
< bag cascade =all,delete-orphanname =childList>
< key column =PARENT_IDnot-null =false/>
<一对多班=小孩/>
< / bag>
< / class>
< class name =Childtable =CHILD>
< id name =idcolumn =CHILD_ID>
< generator class =native/>
< / id>
< / class>
< / hibernate-mapping>

产生

 父母
ID

儿童
CHILD_ID
PARENT_ID

根据你所说的


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


类似于

 父亲= session.get(...); 
parent.getChildren()。clear();

session.update(parent);

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



现在我们来看看下面这个(注意 Assert.assertNull(second)

  public class WhatYouWantTest {

private static SessionFactory sessionFactory;

专用可序列化parentId;

私有序列化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()抛出异常{
父亲=新父();
儿童优先=新儿童();
儿童秒=新儿童();

会话会话= 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 =新Child();
first.setId((Integer)firstId);
$ b $ / **
*它模拟第二个已经被删除的
* /
parent.getChildList()。add(first);

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

session.update(parent);

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

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

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

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

不幸,测试不通过。你可以做什么???


  • 启用长时间运行的对话



Hibernate参考说明


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


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



,您可以创建一个替代映射,使您的Child成为复合元素。因为它的生命周期取决于父对象,所以你可以依靠复合元素来获得你想要的东西。



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

  public class AlternativeParent extends Parent {} 

现在它的映射(Notice Child作为复合元素而不是纯粹的@Entity)

 < ; class name =AlternativeParenttable =PARENT> 
< id name =id>
< generator class =native/>
< / id>
< bag name =childListtable =CHILD>
< key column =PARENT_IDnot-null =false/>
< composite-element class =Child>
< property column =CHILD_IDname =id/>
< / composite-element>
< / bag>
< / class>

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

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

其他孩子=(孩子)o;
//身份相等
//由复合元素使用
if(getId()!= null){
返回新的EqualsBuilder()
.append(getId ),other.getId())
.isEquals();
} else {
//对象相等
}
}

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

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

先Child =新Child();
first.setId((Integer)firstId);
$ b $ / **
*它模拟第二个已经被删除的
* /
parent.getChildList()。add(first);

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

session.update(parent);

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

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

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

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

}

我看到一个绿色栏

I know that deleting orphaned child objects is a common question on SO and a common problem for people new to Hibernate, and that the fairly standard answer is to ensure that you have some variation of cascade=all,delete-orphan or cascade=all-delete-orphan on the child collection.

I'd like to be able to have Hibernate detect that child collection has been emptied/removed from the parent object, and have the rows in the child table deleted from the database when the parent object is updated. For example:

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

My current mapping for the Parent class looks like:

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

This works fine for me when updating an attached object, but I have a use case in which we'd like to be able to take a detached object (which has been sent to our API method by a remote client over HTTP/JSON), and pass it directly to the Hibernate Session - to allow clients to be able to manipulate the parent object in whichever way they like and have the changes persisted.

When calling session.update(parent) on my detached object, the rows in the child table are orphaned (the FK column is set to null) but not deleted. Note that when I'm calling session.update(), this is the first time the Hibernate Session is seeing this object instance - I am not re-attaching or merging the object with the Session in any other way. I'm relying on the client to pass objects whose identifiers correspond to actual objects in the database. For example, the logic in my API service method is something like this:

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

Is it possible for Hibernate to detect orphaned children collections in detached parent objects when passed to session.update(parent)? Or am I mis-using the detached object in some way?

My hope was that I could avoid any sort of complex interactions with Hibernate to persist changes to a detached instance. My API method has no need to further modify the detached object after the call to session.update(parent), this method is merely responsible for persisting changes made by remote client applications.

解决方案

Your mapping (simplified)

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

produces

PARENT
    ID

CHILD
    CHILD_ID
    PARENT_ID

According what you said

I would like to be able to have Hibernate detect that child collection has been removed from the parent object, and have the rows in the child table deleted from the database when the parent object is updated

Something like

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

session.update(parent);

You said it works fine because you have an attached Parent instance

Now let's see the following one (Notice 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();
    }
}

Unfortunately, the test do not pass. What you can do ???

  • Enable a long-running conversation

Hibernate reference says

Extended (or Long) Session - The Hibernate Session may be disconnected from the underlying JDBC connection after the database transaction has been committed, and reconnected when a new client request occurs. This pattern is known as session-per-conversation and makes even reattachment unnecessary. Automatic versioning is used to isolate concurrent modifications and the Session is usually not allowed to be flushed automatically, but explicitely.

disclaimer: i do not have any scenario which uses long running conversation. Java EE Stateful session beans support long running conversation. But its support is for JPA (not Hibernate)

Or you can create an alternative mapping which enables your Child as composite elements. Because its lifecycle depends on the parent object, you can rely on composite elements to get what you want

Create a class named AlternativeParent which extends Parent

public class AlternativeParent extends Parent {}

Now its mapping (Notice Child as composite element instead of plain @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>

Now implement a convenient equals method in the Child 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
     }
}

If i refactor the test case shown above (Now by using AlternativeParent instead)

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

}

I see a green bar

这篇关于更新分离对象时,Hibernate是否可以删除孤立的集合?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆