【发布时间】:2018-02-01 15:19:08
【问题描述】:
关于 JPA 的工作方式,我认为以下是正确的:将相关实体删除或添加到另一个实体(例如,Employee 到 Department.employees)时,关系的双方都必须相应地更新。也就是说,在删除 Employee 的特定实例中,我不应该只是从 EntityManager 中删除它,还要从引用 Department 对象中删除它。不这样做可能会导致其他操作无法正常工作,例如在 Spring Data JPA 示例中:
@Transactional
public void test() {
Function fn = repFun.findOne(75L);
//at this point fn.tests = { Test{76} }
doSomeDelegation(fn.getTests().iterator().next());
fn.setName("new name");
repFun.save(fn);
repFun.flush(); // Whoa! "deleted instance passed to merge"
}
void doSomeDelegation(Test test) {
repTest.delete(test.getId()); //in this example this will delete Test #76
}
但是,为此,我将不得不加载一个 Department 对象,很可能与它的员工集合一起加载(这将导致另一个查询,除非它是预先加载的),否则我可能不需要该数据本次交易。更重要的是,可能还有其他实体引用该员工,所以我必须知道所有可能的引用(这可能是合理的,但有点味道),并通过它们,检查引用端的所有对象(假设可能有一个或多个项目引用该员工,那么我们将需要进行查询以找到所有实际引用它的项目,然后遍历所有发现的实体,将它们与它们的引用一起加载,并更新这些引用) .
这是相当多的工作要做,最令人沮丧的部分是实际上没有必要保持有效的数据库状态!好吧,如果从 Department 到 Employee 的引用是映射到数据库列(即 Department.employee)的属性,而不是集合,那么取消这个引用总是必不可少的;但是如果该引用是集合的一部分,并且集合本质上映射到 Employee 行,那么从 DB 中删除 Employee 实际上就足以拥有一个有效的数据库。无需更新部门,或查找和更新多个项目。更重要的是:如果这样的部门不在 EntityManager 中,那么我们仍然可以跳过更新它,而不是搞砸 JPA 持久性! (但不确定版本更新)。
一个可能的解决方案听起来像“知道可能有部门实体引用被删除的员工,只找到那些已经在 EntityManager 中的实体,然后从他们的引用集合中删除员工”,但我不认为我知道该怎么做,此外,执行删除这样简单的事情是一个非常复杂的逻辑。
那么,在上面的示例中,正确的做法是什么?观察到本应有经验的开发人员如何无法对这个非常简单的问题提出一个好的答案,这真的很有趣。
【问题讨论】:
-
您确实负责部门内员工集合的一致性。但是,如果您的用例是“删除员工”,则此用例中根本不涉及部门,您无需关心部门。只需删除员工,您就完成了。
-
错了,看例子。当然,如果我们确定该事务中除了“按 id 删除”之外没有其他逻辑,那么您是对的。但是,除非您的应用程序特别琐碎,否则您无法始终确定这一点。在前面的示例中,doSomeDelegation 甚至可能是某个其他类的一部分。它甚至可能没有接收到作为参数的删除目标,但由于一些与 test() 无关的孤立逻辑,意外地决定删除这个对象。由于 test() 没有显式添加相同的测试,所以应该没有冲突,但它确实发生了。
-
当然,如果我们确定此事务中除了“按 id 删除”之外没有其他逻辑:那么,您应该知道这一点。如果您知道部门及其员工列表涉及删除员工的用例,那么用例应该确保保持一致性。关键是:这是业务逻辑的责任。低级 deleteById() 存储库方法不负责维护所有可能的关联。
-
好的我明白你的回答,谢谢。所以我不能将我的一些服务用作黑匣子,而必须确切地知道它们在做什么。这肯定可以解决问题,但我不能完全同意你的看法。希望看到其他建议。
-
但是我想即使我不必记住每个 API 调用中发生的所有事情,但我真的必须在我要在其中进行更改时刷新这些知识方法,因为除了偶然发现 JPA 限制之外,还有其他方法可以引入冲突。所以也许,也许你是对的。
标签: hibernate jpa spring-data spring-data-jpa