EntityManager无法remove entity的问题

今天遇到一个奇怪的事情,利用EntityManager.remove(entity)方法删除一个entity时,删不掉,也不报错。后来经过多方查证,解决了这个问题。

ERD

Entity定义

------------- 第一个Entity A ---------------
@Entity
public class A {
    @Id
    private Long id;
    
    @Column(nullable = false, unique = true, length = 60)
    private String internalKey;
    
    @OneToMany(mappedBy = "b", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<B> bs = new ArrayList<>();
    ...
}
------------- 第二个Entity B ---------------
@Entity
public class B {
    @Id
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "A_internalKey", referencedColumnName = "internalKey")
    private A a;
    ...
}

数据

Table A:
id       internalKey
-------- -------------
1        a1

Table B:
id       A_internalKey
-------- -------------
1        a1
2        a1

问题

按照多年SQL脚本操作数据的经验,直接从B表中删除记录b(id:2)是可行的。A表上不存在任何对B表的外键引用,所以可以直接删除B表上的数据,数据库管理系统不会不开心。但是,使用JPA中EntityManager的remove(entity)方法来删除b(id:2)时,问题发生了。remove根本删不掉b(id:2)记录,别说数据库中的记录了,连SQL语句都没有从JEE container中发出来。而且,更要命的是,没!有!报!错!

我做了一个替换方案,用JPQL语句直接删除B(id:2),结果成功了。呵呵,到此可不算完结,不然我也不用大费周章的把这件事情记录下来。在删除
B(id:2)之后,我又尝试保存对A所做的变更,这么一保存,又出问题了。JPA报错,说是B(id:2)找不到,我晕。这又是什么情
况,B(id:2)明明已经被我删掉了,怎么在persist
A的时候JPA却要去检查一个已经被删掉的object?我确信在用JPQL删掉了B(id:2)后,我手动从A.bs集合中剔除了B(id:2),为啥
这个B(id:2)阴魂不散呢?

分析

在翻阅了一些文档后,我隐约意识到,问题应该与entity的几种状态(尤其是detached状态)以及O/R Mapping框架中的缓存有关。说白了,就是程序哪里产生数据不一致了。一般,之所以产生这种不一致问题可能与受管对象的状态、生命周期或是访问范围等有关。那么,代入JPA中考虑,对应的应该是Entity的生命周期或访问机制(缓存机制)。

继续深究发现,这个issue是由于多个方面综合作用下产生的。

首先,问题的最关键之处:A与B的bidirectional OneToMany(双向一对多关系)。

这其实很好理解,就像Java中的垃圾回收机制一样,被用到的Object不会被GC。同理,被引用的child,也就是这个B(id:2)啦,一直被A(id:1)引用着呢,JPA怎么会让你把他干掉?!。前面未曾提及,在删除B(id:2)之前,A(id:1)被JPA读取过。当我试图删除B(id:2)时A(id:1)应该还在JPA的缓存里待着。根据Entity上的annotation标注,A(id:1)应该同时保有B(id:1)、B(id:2)的引用(就是那个List<B> bs集合中的两个元素)。JPA的remove出于某种保护,并不会让你把被引用的B(id:2)删掉。

当然,如果你执意要删除,那么可以用entityManager.createQuery("DELETE FROM B WHERE B.id=2").executeUpdate();来强行删除指定的数据库记录。因为createQuery().executeUpdate()会向DBMS发送指定的sql,如果有报错,异常会由DBMS通过底层JDBC报给JPA框架最终通过EntityManager冒出来。我就是用了这种方法强行把B(id:2)给干掉了。

接下来要说的,就是因素二:缓存与实际数据库不一致

看上面的那段标红的内容。是不是想到了什么?在删除B(id:2)之前,A(id:1)带着对B(id:1)和B(id:2)的引用一直待在缓存里。当B(id:2)被我用JPQL强行删除之后,并没有任何代码去更新缓存里的A(id:1),所以A(id:1)上应该还有B(id:2)的引用。接下来,要persist A(id:1)的改动。虽然我后来手动做了A.bs.remove(B(id:2))(从bs集合中剔除了B(id:2)的引用),但很遗憾,A(id:1)已经处于detached状态(即游离状态,姑且把已经处于游离状态的A(id:1)叫做a(id:1))。对一个已经处于游离状态的object进行的改动,不会映射到对应的Entity上,换句话说,不论我怎样操作a(id:1),在JPA缓存中的A(id:1)不会被更新。而且,戏剧性的一幕发生了,当我尝试着去persist一个游离对象a(id:1)时,JPA通过a(id:1).equals(A(id:1))的比较,认为a(id:1) == A(id:1),因为两个对象的id一样,hashcode一样,所以JPA从缓存中找到A(id:1),试图再次persist一遍,接下来的事情也就不用我说了,JPA报错,并提示我找不到B(id:2)。(什么?为什么会去找B(id:2)?哦,那是因为A上的Cascade定义,)

解决方案

我这里有两种解决方案:

1、以更新A为起始点,剔除B(id:2)后,persist A。由于A上设置的Cascade,在更新A的同时,JPA会级联删除B(id:2)

2、可以强行删除B(id:2),但在对A(id:1)进行任何操作前,先去fecth一下A(id:1),也就是强行刷新一下JPA的缓存。

我个人推荐第一种方案。

时间: 2024-12-10 20:43:12

EntityManager无法remove entity的问题的相关文章

浅析JPA中EntityManager无法remove entity的问题

今天遇到一个奇怪的事情,利用EntityManager.remove(entity)方法删除一个entity时,删不掉,也不报错.后来经过多方查证,解决了这个问题. ERD Entity定义 ------------- 第一个Entity A --------------- @Entity public class A {     @Id     private Long id;          @Column(nullable = false, unique = true, length =

EntityManager方法简介

EntityManager 是用来对实体Bean 进行操作的辅助类.他可以用来产生/删除持久化的实体Bean,通过主键查找实体bean,也可以通过EJB3 QL 语言查找满足条件的实体Bean.实体Bean 被EntityManager 管理时,EntityManager跟踪他的状态改变,在任何决定更新实体Bean 的时候便会把发生改变的值同步到数据库中.当实体Bean 从EntityManager 分离后,他是不受管理的,EntityManager 无法跟踪他的任何状态改变.EntityMan

利用Mocking Framework 单元测试Entity Framework

一.前言 在实际编写程序时,往往需要与数据库打交道,在单元测试中直接使用数据库又显得太重,如果可以方便的编写一些测试数据,这样更易于检测功能.如何模拟数据库行为便是本篇的主题.微软有教程说明Moq Entity Framework,需注意的是EF的版本必须是6以上.但在这篇教程中是直接使用DbContext,而自己的应用程序中都是用UnitOfWork模式.经过修改后也可以实现类似功能. 二.参考文献 https://msdn.microsoft.com/en-us/data/dn314429

【转】What is an entity system framework for game development?

What is an entity system framework for game development? Posted on 19 January 2012 Last week I released Ash, an entity system framework for Actionscript game development, and a number of people have asked me the question “What is an entity system fra

[转]Upgrading to Async with Entity Framework, MVC, OData AsyncEntitySetController, Kendo UI, Glimpse &amp; Generic Unit of Work Repository Framework v2.0

本文转自:http://www.tuicool.com/articles/BBVr6z Thanks to everyone for allowing us to give back to the .NET community, we released v1.0 of the Generic Unit of Work and Repository Framework for four weeks and received 655 downloads and 4121 views. This po

Entity Framework Tutorial Basics(41):Multiple Diagrams

Multiple Diagrams in Entity Framework 5.0 Visual Studio 2012 provides a facility to split the design time visual representation of the Entity Data Model. This means that you can have multiple diagrams for one Entity Data Model. You can create a new d

关于Entity Framework中的Attached报错的完美解决方案

我们在使用Entity Framework进行CRUD时,为了提升查询效率,一般均会启动NoTracking,即不追踪变化,设置代码如下: //这是DB First模式下设置方法: aTestEntities db = new aTestEntities(); db.Companies.MergeOption = MergeOption.NoTracking; //这是CODE First及Model First模式下设置方法: aTestEntities db = new aTestEntit

比较JPA的EntityManager接口与Hibernate的Session接口

本文对JPA的EntityManager接口与Hibernate的Session接口进行了比较,本文参考<精通JPA与Hibernate:Java对象持久化技术详解>第3版,2020年出版,作者:孙卫琴. EntityManager接口与Session接口的许多功能很相似,以下表1-1对这两个接口的方法做了对比.表1-1 对比EntityManager接口与Session接口 从以上表可以看出,EntityManager接口中的大部分方法在Sesson接口中都有对应的方法.EntityMana

10、JPA_映射双向多对多的关联关系

双向多对多的关联关系 双向多对多的关联关系(抽象成A-B)具体体现:A中有B的集合的引用,同时B中也有对A的集合的引用.A.B两个实体对应的数据表靠一张中间表来建立连接关系. 同时我们还知道,双向多对多的关联关系可以拆分成三张表,两个双向多对一关联关系.拆分以后还是有一张中间表,其好处就是可以在中间表中添加某些属性用作其它.这个后面会讲解.而单纯的双向多对多关联关系的中间表有两个外键列,无法增加其它属性. 本节只讲单纯的双向多对多关联关系.从例子讲解配置方法和原理: 有“商品Item”和“类别C