当前位置: 首页 > 面试题库 >

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

段干开宇
2023-03-14
问题内容

以一对多关系(国家->)的非常简单的例子。

国家(反面):

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

StateTable(所有者):

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

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

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

应当注意,orphanRemoval设置为true。该StateTable实体由客户端应用程序提供,该客户端应用程序有兴趣将实体关联CountrycountryId = 67)更改StateTable为其他内容(countryId = 68)(因此在JPA的反面,将子实体从其父(集合)迁移到另一个父(集合),orphanRemoval=true反过来会反对)。

Hibernate提供程序发出DELETEDML语句,使对应于该StateTable实体的行从基础数据库表中删除。

尽管orphanRemoval设置为true,但我希望Hibernate发出常规的UPDATEDML语句,导致orphanRemoval完全暂停其作用,因为关系链接已迁移(而不是简单删除)。

EclipseLink正是完成了这项工作。它UPDATE在给定的场景中发出一条语句(与orphanRemoval设置为具有相同的关系true)。

根据规范的行为?UPDATE除了orphanRemoval从反面删除之外,在这种情况下是否可以使Hibernate发出语句?

这只是使双向关系在双方上更加一致的一种尝试。

防守链接管理方法即add()remove()在上述代码段,用于如果需要,在所定义的Country实体如下。

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

更新:

UPDATE如果给定的代码以以下方式修改,则Hibernate只能发出预期的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(),那么将DELETE意外发出DML语句。

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

我了解EntityManager#getReference()和之间的基本区别EntityManager#find()


问题答案:

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

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

注意,我仅oldState.getCountry().hashCode()在第三行中添加了内容。现在,您可以通过仅删除此行来重现问题。

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

3.2.4 节:

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

  • 如果X是受管实体,则将其同步到数据库。
    • 对于由X的关系引用的所有实体Y,如果已使用级联元素值Cascade = PERSIST或Cascade =
      ALL注释了与Y的关系,则对Y应用持久性操作


3.2.2 节:

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

  • 如果X是已删除的实体,则它将被管理。

orphanRemovalJPA
javadoc:

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

正如我们所看到的,它orphanRemoval是根据remove操作定义的,因此所有适用的规则也remove 必须
适用orphanRemoval

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

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

  1. 当出现多余的行(hashCode)时:

    1. Hibernate oldCountry与数据库同步。它会在处理之前完成newCountry,因为oldCountry首先已加载(通过调用强制进行代理初始化hashCode)。
    2. Hibernate看到StateTable实例已从oldCountry的集合中删除,因此将该StateTable实例标记为已删除。
    3. Hibernate newCountry与数据库同步。该PERSIST操作级联到,该操作stateTableList现在包含已删除的StateTable实体实例。
    4. StateTable现在,将再次管理已删除的实例(上面引用的JPA规范的3.2.2节)。
    5. 当多余的行(hashCode)不存在时:

    6. Hibernate newCountry与数据库同步。它在处理之前就完成了oldCountry,因为newCountry首先被加载(带有entityManager.find)。

    7. Hibernate oldCountry与数据库同步。
    8. Hibernate看到StateTable实例已从oldCountry的集合中删除,因此将该StateTable实例标记为已删除。
    9. StateTable实例的删除与数据库同步。

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

那么,这是否符合JPA规范?显然可以,没有违反JPA规范规则。

为什么这不便携?

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

另外,这取决于您对“可移植性”的看法。orphanRemoval就其正式定义而言,此功能和任何其他JPA功能都是可移植的。但是,这取决于您如何结合使用它们以及JPA提供程序的详细信息。

顺便说一句,规范的 2.9 节建议(但没有明确定义)orphanRemoval

否则,可移植应用程序不得依赖于特定的删除顺序,并且不得将已孤立的实体重新分配给另一个关系,也不能尝试将其持久化。

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



 类似资料:
  • 举一个非常简单的一对多关系的例子(国家 实体的方法: 应该注意,<code>orphanRemove</code>设置为<code>true</code>。<code>StateTable</code>实体由一个客户端应用程序提供,该应用程序有兴趣将<code>State Table</code>中的实体关联<code>Country</code<(<code>countryId=67</code

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

  • 问题内容: 当业务层创建一个新的实体时,该实体在逻辑上表示应该更新的现有实体的实例(例如,它们共享相同的业务密钥),这是合并不良做法的方法吗? 我问是因为在分离的实体上显式设置ID对我来说很奇怪,但是即使User实体的equals和hashcode方法得到了适当实现,在这里设置ID是确保合并发生的唯一方法。 有更好的做法吗? 此方法是否有特定的缺点,以后会困扰我? 谢谢参观! 问题答案: 该代码将

  • 我的两个实体有一对一的关系 我尝试通过此方法删除我的用户实体 PasswordResetTokenRepository类,我在服务方法中调用了该类,用于删除用户,我使用了常规Hibernate方法deleteById(Long id) 但是当我尝试通过此方法删除时,出现此错误:not-null 属性引用 null 或瞬态值:kpi.diploma.ovcharenko.entity.user.Pa

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

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