Java 当接收到ID为的实体时,Hibernate会话#merge是否应该执行插入操作?
这似乎会经常出现,但我在谷歌上搜索了一下,却一无所获 假设您有一个Hibernate实体Java 当接收到ID为的实体时,Hibernate会话#merge是否应该执行插入操作?,java,hibernate,jpa,concurrency,Java,Hibernate,Jpa,Concurrency,这似乎会经常出现,但我在谷歌上搜索了一下,却一无所获 假设您有一个Hibernate实体用户。您的数据库中有一个id为1的用户 您有两个线程在运行,A和B。它们执行以下操作: A获取用户1并关闭其会话 B获取用户1并删除它 A更改用户1上的字段 A获取新的会话和合并s用户1 我所有的测试都表明,merge试图在数据库中找到用户1(显然它不能),因此它插入了一个id为2的新用户 另一方面,我的期望是Hibernate会看到被合并的用户不是新用户(因为它有一个ID)。它将尝试在数据库中查找用户,
用户
。您的数据库中有一个id为1的用户
您有两个线程在运行,A和B。它们执行以下操作:
- A获取用户1并关闭其会话
- B获取用户1并
删除它
- A更改用户1上的字段
- A获取新的
和会话
s用户1合并
merge
试图在数据库中找到用户1(显然它不能),因此它插入了一个id为2的新用户
另一方面,我的期望是Hibernate会看到被合并的用户不是新用户(因为它有一个ID)。它将尝试在数据库中查找用户,但会失败,因此不会尝试插入或更新。理想情况下,它会抛出某种并发异常
请注意,我使用的是通过@Version
进行的乐观锁定,这对解决问题没有帮助
因此,问题是:
EntityManager
上调用merge
而不是在Hibernate会话上调用merge
时是否会出现相同的行为
1.我观察到的冬眠行为是否为预期行为 这种行为是正确的。如果必须将操作拆分为两个会话,您只需尝试执行不受并发数据修改保护的操作:)。只需再次找到要更新的对象并检查它是否仍然存在,如果不存在则抛出异常。如果有,则使用em(类、主键、LockModeType)将其锁定;或者使用@Version或@Entity(optimisticLock=OptimisticLockType.ALL/DIRTY/Version)保护对象直到事务结束 2.如果是这样,在JPA EntityManager上而不是在Hibernate会话上调用merge时是否有相同的行为 可能:是的 3.如果答案是2。是的,为什么没有人抱怨呢 因为如果使用悲观或乐观锁定来保护操作,问题将消失:)
您试图解决的问题称为:不可重复读取请参阅下面hibernate文档中的文本 将给定对象的状态复制到具有相同标识符的持久对象上。如果当前没有与会话关联的持久实例,则将加载该实例。返回持久实例。如果给定实例未保存,请保存其副本并将其作为新的持久实例返回。 它清楚地说明了复制数据库中对象的状态(数据)。如果对象不存在,则保存该数据的副本。当我们说保存副本时,hibernate总是创建一个带有新标识符的记录 Hibernate合并函数的工作原理如下
锁定用于控制并发问题。这不是并发性问题。我一直在研究JSR-220,从中可以看到
Session#merge
。遗憾的是,我发现JSR模棱两可
它确实说:
乐观锁定是一种用于确保更新
与实体状态相对应的数据库数据
仅当自
实体状态被读取
如果您将“更新”包括数据库数据的一般变化,包括删除,而不仅仅是SQL更新(我是这么做的),我认为您可以提出一个论点,即观察到的行为不符合乐观锁定
鉴于对我的问题的评论以及随后发现的问题,许多人都同意
从纯粹实用的角度来看,这种行为,无论是否符合,都可能导致相当多的bug,因为它与许多开发人员的期望相反。似乎没有一个简单的解决办法。事实上,Spring Data JPA似乎完全盲目地忽略了这个问题。也许其他JPA提供商的处理方式有所不同,但使用Hibernate可能会导致问题
我目前正在使用Session#update
解决这个问题。它真的很难看,当您尝试更新已分离的实体时,需要代码来处理这种情况,并且已经有了该实体的托管副本。但是,它也不会导致虚假插入。您是否尝试过使用乐观锁定(@Version
)?这可能会捕获插入具有版本集的行。然而,我不确定。事实上,我已经知道了。它不会影响结果。但是,是的,你也希望这会有所帮助。我也对这种行为感到惊讶。对我来说就像一只巨大的虫子。你用的是哪个版本?看起来像是1661年我选出来的一个bug,因为它似乎得到了更多的关注。我还将972标记为副本。我们还能做些什么来引起更多的注意吗?不,事实上。正如我在问题中所说,乐观锁定无助于这种情况。悲观锁定可能会起作用(我没有使用过),但对于一个可以很容易解决的问题来说,这是一个相当苛刻的解决方法。嗯,为什么乐观锁定没有帮助?如果再次找到该对象,则hibernate将执行“更新X,其中版本=?”而不是insert语句。我