当前位置: 首页 > 知识库问答 >
问题:

防止HiberNate删除孤立实体,同时合并具有与孤立实体关联的实体删除设置为true的实体

通沛
2023-03-14

举一个非常简单的一对多关系的例子(国家 -

国家(反面) :

@OneToMany(mappedBy = "country", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<StateTable> stateTableList=new ArrayList<StateTable>(0);

状态表(拥有方):

@JoinColumn(name = "country_id", referencedColumnName = "country_id")
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private Country country;

尝试在活动数据库事务(JTA或资源本地)中更新提供的(分离的)< code>StateTable实体的方法:

public StateTable update(StateTable stateTable) {

    // Getting the original state entity from the database.
    StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId());
    // Get hold of the original country (with countryId = 67, for example).
    Country oldCountry = oldState.getCountry();
    // Getting a new country entity (with countryId = 68) supplied by the client application which is responsible for modifying the StateTable entity.
    // Country has been changed from 67 to 68 in the StateTable entity using for example, a drop-down list.
    Country newCountry = entityManager.find(Country.class, stateTable.getCountry().getCountryId());
    // Attaching a managed instance to StateTable.
    stateTable.setCountry(newCountry);

    // Check whether the supplied country and the original country entities are equal.
    // (Both not null and not equal - http://stackoverflow.com/a/31761967/1391249)
    if (ObjectUtils.notEquals(newCountry, oldCountry)) {
        // Remove the state entity from the inverse collection held by the original country entity.
        oldCountry.remove(oldState);
        // Add the state entity to the inverse collection held by the newly supplied country entity
        newCountry.add(stateTable);
    }

    return entityManager.merge(stateTable);
}

应该注意,<code>orphanRemove</code>设置为<code>true</code>。<code>StateTable</code>实体由一个客户端应用程序提供,该应用程序有兴趣将<code>State Table</code>中的实体关联<code>Country</code<(<code>countryId=67</code))更改为其他实体(<code<countryId>68</code〕)(因此在JPA中相反,将子实体从其父实体(集合)迁移到另一个父实体(集合orphanRemove=true将反过来反对)。

Hibernate provider发出一个< code>DELETE DML语句,导致对应于< code>StateTable实体的行从底层数据库表中删除。

尽管<code>orphanRemove</code>被设置为<code>true</code<,但我希望Hibernate发出一个常规的<code>UPDATE</code〕DML语句,导致<code>morphanRemoval</code}的效果完全被挂起,因为关系链接被迁移(而不是简单地删除)。

Eclipse Link正是做这项工作的。它在给定的场景中发出UPDATE语句(与设置为true的具有相同的关系)。

哪一个行为符合规范?在这种情况下,除了从反面删除<code>orphanRemove</code>之外,是否可以让Hibernate发出<code>UPDATE</code<语句?

这只是试图使双方的双向关系更加一致。

如有必要,上述片段中使用的防御链路管理方法即add()

public void add(StateTable stateTable) {
    List<StateTable> newStateTableList = getStateTableList();

    if (!newStateTableList.contains(stateTable)) {
        newStateTableList.add(stateTable);
    }

    if (stateTable.getCountry() != this) {
        stateTable.setCountry(this);
    }
}

public void remove(StateTable stateTable) {
    List<StateTable> newStateTableList = getStateTableList();

    if (newStateTableList.contains(stateTable)) {
        newStateTableList.remove(stateTable);
    }
}

更新:

Hibernate只能发出一个预期的< code>UPDATE DML语句,前提是给定的代码以下列方式修改。

public StateTable update(StateTable stateTable) {
    StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId());
    Country oldCountry = oldState.getCountry();
    // DELETE is issued, if getReference() is replaced by find().
    Country newCountry = entityManager.getReference(Country.class, stateTable.getCountry().getCountryId());

    // The following line is never expected as Country is already retrieved 
    // and assigned to oldCountry above.
    // Thus, oldState.getCountry() is no longer an uninitialized proxy.
    oldState.getCountry().hashCode(); // DELETE is issued, if removed.
    stateTable.setCountry(newCountry);

    if (ObjectUtils.notEquals(newCountry, oldCountry)) {
        oldCountry.remove(oldState);
        newCountry.add(stateTable);
    }

    return entityManager.merge(stateTable);
}

请注意更新版本代码中的以下两行。

// Previously it was EntityManager#find()
Country newCountry = entityManager.getReference(Country.class, stateTable.getCountry().getCountryId());
// Previously it was absent.
oldState.getCountry().hashCode();

如果最后一行不存在或EntityManager#getReference()EntityManager#find()替换,则会意外发出DELETEDML语句。

那么,这是怎么回事呢?我特别强调便携性。不在不同的 JPA 提供程序之间移植这种基本功能会严重破坏 ORM 框架的使用。

我了解EntityManager#getReferences()EntityManager#find()之间的基本区别。


共有2个答案

谷梁德容
2023-03-14

一旦您引用的实体可以在其他父级中使用,它就会变得复杂。为了真正使其干净,ORM必须在删除之前在数据库中搜索已删除实体的任何其他用法(持久垃圾收集)。这很耗时,因此不是真正有用,因此无法在 Hibernate 中实现。

只有当您的孩子用于单亲并且从未在其他地方重用时,删除孤儿才有效。当试图重用它来更好地检测对该特性的误用时,您甚至可能会得到一个异常。

决定是否要保留删除孤儿。如果要保留它,则需要为新父级创建一个新子级,而不是移动它。

如果您放弃删除孤立项,则必须在不再引用子项时立即自行删除它们。

董翰墨
2023-03-14
匿名用户

首先,让我们将您的原始代码更改为更简单的形式:

StateTable oldState = entityManager.find(StateTable.class, stateTable.getStateId());
Country oldCountry = oldState.getCountry();
oldState.getCountry().hashCode(); // DELETE is issued, if removed.

Country newCountry = entityManager.find(Country.class, stateTable.getCountry().getCountryId());
stateTable.setCountry(newCountry);

if (ObjectUtils.notEquals(newCountry, oldCountry)) {
    oldCountry.remove(oldState);
    newCountry.add(stateTable);
}

entityManager.merge(stateTable);

注意,我只添加了< code>oldState.getCountry()。第三行的hashCode()。现在,您只需移除这一行即可重现您的问题。

在我们解释这里发生的事情之前,首先从JPA 2.1规范中摘录一些内容。

第3.2.4节:

应用于实体X的刷新操作的语义如下:

    < li >如果X是受管实体,它将与数据库同步。 < ul > < li >对于来自X的关系所引用的所有实体Y,如果与Y的关系已经用cascade元素值cascade=PERSIST或cascade=ALL进行了注释,则PERSIST操作将应用于Y

第 3.2.2 节:

应用于实体X的持久化操作的语义学如下:

  • 如果 X 是已删除的实体,则它将成为托管实体。

孤立删除JPA javadoc:

(可选)是否将删除操作应用于已从关系中删除的实体,以及是否将删除操作级联到这些实体。

如我们所见,orphanRemoval是根据删除操作定义的,因此所有适用于删除的规则也必须适用于orphanRemoval

其次,如本答案所述,Hibernate执行的更新顺序是实体在持久性上下文中加载的顺序。更准确地说,更新实体意味着将其当前状态(脏检查)与数据库同步,并将 PERSIST 操作级联到其关联。

现在,这就是你的情况。在事务结束时,Hibernate将持久性上下文与数据库同步。我们有两种情况:

> < li>

当存在额外的行(< code>hashCode)时:

  1. Hibernate将oldCountry与数据库同步。它在处理newCountry之前执行此操作,因为首先加载了oldCountryhashCode强制进行代理初始化)
  2. Hibernate发现一个<code>StateTable</code>实例已从<code>oldCountry</code}的集合中删除,因此将<code>StateTable</code>实例标记为已删除
  3. Hibernate将newCountry与数据库同步。PERSIST操作级联到stateTableList,该列表现在包含已删除的StateTable实体实例
  4. 删除的<code>StateTable</code>实例现在再次被管理(上面引用的JPA规范的3.2.2节)

当额外的行(hashCode)不存在时:

  1. HiberNate将new与数据库同步。它在处理old之前执行此操作,因为new是先加载的(使用entityManager.find)。
  2. HiberNate将oldhtml" target="_blank">数据库同步。
  3. HiberNate看到Statetable实例已从old的集合中删除,因此将Statetable实例标记为已删除。
  4. Statetable实例的删除与数据库同步。

更新的顺序还解释了您的发现,其中您基本上强制在从数据库加载 newCountry 之前进行 oldCountry 代理初始化。

那么,这是否符合JPA规范?显然是的,没有JPA规范规则被打破。

这怎么不是便携的

JPA规范(毕竟与任何其他规范一样)为提供者提供了定义规范未涵盖的许多细节的自由。

此外,这取决于您对“可移植性”的看法。orphanRemoval 功能和任何其他 JPA 功能在正式定义方面都是可移植的。但是,这取决于您如何结合 JPA 提供程序的具体情况使用它们。

顺便说一下,规范的第 2.9 节建议(但没有明确定义)孤立删除

否则,可移植的应用程序必须不依赖于特定的移除顺序,并且不能将已被孤立的实体重新分配给另一个关系或试图保持它。

但这只是规范中模糊或定义不明确的建议的一个示例,因为规范中的其他语句允许保留已删除的实体。

 类似资料:
  • 问题内容: 以一对多关系(国家)的非常简单的例子。 国家(反面): StateTable(所有者): 尝试更新活动数据库事务(JTA或资源本地)中提供的(分离的)实体的方法: 应当注意,设置为。该实体由客户端应用程序提供,该客户端应用程序有兴趣将实体关联()更改为其他内容()(因此在JPA的反面,将子实体从其父(集合)迁移到另一个父(集合),反过来会反对)。 Hibernate提供程序发出DML语

  • 下面是场景: 容器和项都可以存在排他性。ItemContainer只能包含对现有项和容器的引用 我想要做的是能够删除一个项目,并删除它对应的ItemContainer行(使用孤儿删除)。任何容器都应该仍然存在。 编辑1:正如JB所要求的:下面是正在使用的相关代码。 请求来自一个jsp 值得一提的是,我试图在我的jUnit测试中做同样的事情(如果需要,我可以发布测试代码和任何相关的类),但是我没有收

  • 删除父实体时,我还想删除关联的子实体(从数据库中)。我试图在删除时使用级联,如下所示,但我一定做错了什么。 当对父实体对象调用删除时,我收到错误消息:“该实体仍在数据库的其他地方引用”。我可以确认该实体在数据库的其他地方引用的唯一地方是在下面的两个表中(如果我手动从数据库中删除子行,对父实体对象的删除调用工作正常)。在过去的9个小时里,我一直在阅读实体对象并尝试不同的东西。我做错了什么? 这是我的

  • 我试图实现的是一个创建/编辑用户工具。可编辑字段有: null null 注意:findAllRolesExceptOwnedByUser()是一个自定义的存储库函数,返回所有角色(那些尚未分配给$User的角色)的子集。 1.3.1添加角色: 1.3.2删除角色: 由于组和角色有相同的问题,我在这里跳过它们。如果我让角色发挥作用,我知道我也能在团队中做到这一点。 没有必要发布这些内容,因为它们工

  • 问题内容: 我有两个分别以双向一对多关系存在的实体类 A 和 B。 A.java: B.java 在一个简单的控制台应用程序中,我从数据库中获取了特定的 A 行,并尝试删除其详细信息 B 行(随机),但是 JPA / Hibernate* 不仅删除了该行-甚至没有向该行发出任何 DELETE 语句。数据库。删除 B 行的唯一方法是从 A.java 的集合( LinkedHashSet )中删除相应

  • 主要内容:JPA实体删除示例要从数据库中删除记录,可以使用接口提供方法。方法使用主键来删除特定的记录。 JPA实体删除示例 在这里,我们将演示如何根据主键删除指定学生的信息。 完整的项目代码如下所示 - 这个例子包含以下步骤 - 第1步: 在包下创建一个名为的实体类,它包含属性:,和。 文件:StudentEntity.java 的代码如下 - 第2步: 将实体类和其他数据库配置映射到文件中。 文件:persistence.