以一对多关系(国家->)的非常简单的例子。
->
国家(反面):
@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或资源本地)中提供的(分离的)实体的方法:
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); }
应当注意,orphanRemoval设置为true。该StateTable实体由客户端应用程序提供,该客户端应用程序有兴趣将实体关联Country(countryId = 67)更改StateTable为其他内容(countryId = 68)(因此在JPA的反面,将子实体从其父(集合)迁移到另一个父(集合),orphanRemoval=true反过来会反对)。
orphanRemoval
true
Country
countryId = 67
countryId = 68
orphanRemoval=true
Hibernate提供程序发出DELETEDML语句,使对应于该StateTable实体的行从基础数据库表中删除。
DELETE
尽管orphanRemoval设置为true,但我希望Hibernate发出常规的UPDATEDML语句,导致orphanRemoval完全暂停其作用,因为关系链接已迁移(而不是简单删除)。
UPDATE
EclipseLink正是完成了这项工作。它UPDATE在给定的场景中发出一条语句(与orphanRemoval设置为具有相同的关系true)。
根据规范的行为?UPDATE除了orphanRemoval从反面删除之外,在这种情况下是否可以使Hibernate发出语句?
这只是使双向关系在双方上更加一致的一种尝试。
防守链接管理方法即add()与remove()在上述代码段,用于如果需要,在所定义的Country实体如下。
add()
remove()
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语句。
EntityManager#getReference()
EntityManager#find()
那么,这是怎么回事?特别是,我强调可移植性。不能在不同的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()在第三行中添加了内容。现在,您可以通过仅删除此行来重现问题。
oldState.getCountry().hashCode()
在我们解释这里发生的事情之前,首先请摘录JPA 2.1规范的一些摘录。
第 3.2.4 节:
应用于实体X的刷新操作的语义如下: 如果X是受管实体,则将其同步到数据库。 对于由X的关系引用的所有实体Y,如果已使用级联元素值Cascade = PERSIST或Cascade = ALL注释了与Y的关系,则对Y应用持久性操作
应用于实体X的刷新操作的语义如下:
第 3.2.2 节:
应用于实体X的persist操作的语义如下: 如果X是已删除的实体,则它将被管理。
应用于实体X的persist操作的语义如下:
orphanRemovalJPA javadoc:
(可选)是否 将删除操作 应用于已从关系中删除的实体,以及是否将删除操作应用于这些实体。
正如我们所看到的,它orphanRemoval是根据remove操作定义的,因此所有适用的规则也remove 必须 适用orphanRemoval。
remove
其次,如该答案所述,Hibernate执行的更新顺序是在持久性上下文中加载实体的顺序。更准确地说,更新实体意味着将其当前状态(脏检查)与数据库同步,并将PERSIST操作级联到其关联。
PERSIST
现在,这就是您的情况。在事务结束时,Hibernate将持久性上下文与数据库同步。我们有两种情况:
当出现多余的行(hashCode)时:
hashCode
oldCountry
newCountry
stateTableList
当多余的行(hashCode)不存在时:
Hibernate newCountry与数据库同步。它在处理之前就完成了oldCountry,因为newCountry首先被加载(带有entityManager.find)。
entityManager.find
更新的顺序还解释了您的发现,在这些发现中,您基本上oldCountry是在newCountry从数据库加载之前强制进行了代理初始化。
那么,这是否符合JPA规范?显然可以,没有违反JPA规范规则。
为什么这不便携?
JPA规范(毕竟像其他任何规范一样)使提供者可以自由定义规范未涵盖的许多细节。
另外,这取决于您对“可移植性”的看法。orphanRemoval就其正式定义而言,此功能和任何其他JPA功能都是可移植的。但是,这取决于您如何结合使用它们以及JPA提供程序的详细信息。
顺便说一句,规范的 2.9 节建议(但没有明确定义)orphanRemoval:
否则,可移植应用程序不得依赖于特定的删除顺序,并且不得将已孤立的实体重新分配给另一个关系,也不能尝试将其持久化。
但这只是规范中含糊不清或未明确定义的建议的一个示例,因为规范中的其他语句允许持久保留已删除的实体。