【问题标题】:@Version column gets updated even though persist was never called即使从未调用过持久化,@Version 列也会更新
【发布时间】:2011-08-02 09:58:46
【问题描述】:

我有一个包含两个表的数据库(实际上更重要,但在这种情况下这无关紧要)。两者之一具有对另一个的外键引用。这些可能是映射:

@Entity @Table public class A {
   @Id public String id;
   @Version public int version;
   @OneToMany(targetEntity = B.class,
              mappedBy = "a",
              cascade = CascadeType.ALL,
              fetch = FetchType.LAZY)
   public Collection<B> bs;

   ...
}

@Entity @Table public class B {
   @Id public String id;
   @Version public int version;
   @ManyToOne(targetEntity = A.class,
              cascade = CascadeType.ALL,
              fetch = FetchType.LAZY)
   public A a;

   ...
}

我有一个中间数据结构,它包含有关AB 之间关系的信息,我在将我的数据保存在活动事务中之前对其进行迭代,检查AB 是否已经存在数据库是否使用EntityManager.find(Class&lt;T&gt;, Object):

// get an entity manager and begin transaction
for (C c : cs) {
    A a = em.find(A.class, c.getAId()); // an `A` doesn't exist in the database
    if (null == a) {                    // on first iteration so a new one will
        a = new A();                    // be created
        a.id = c.getAId();              // however in a 100 `C` there are 4-5
    }                                   // unique `A` exists

    B b = em.find(B.class, c.getBId()); // a `B` generally doesn't exist in
    if (null == b) {                    // the database, but the opposite
        b = new B();                    // could happen often
        b.id = c.getBId();
        b.a = a;
        a.bs.add(b);
    }       

    if (!em.contains(b))                // if a `B` was found in the database
      em.persist(b);                    // persist won't execute here
}
// commit transaction

这很好用:新数据会被持久化,而现有的数据不会。

但是!无论在 for-each 循环中发生什么,我的数据库中的版本列总是都会更新。我已经获取了一组样本数据,将其保存在我的空表中(版本为 0);我已经获取了同样的一组样本数据并再次保存它(EntityManager.persist(Object) 从未被调用)并且版本字段(A)得到了更新,但我无法想象为什么。

我知道即使 EntityManager.persist(Object) 从未被调用,我的持久性提供程序 (Hibernate) 也会生成更新语句来增加版本列。

问题:为什么?我怎样才能防止这种情况?为了避免这种情况,我应该对我的数据持久化方法进行哪些更改?谢谢。

【问题讨论】:

  • 我无法重现这个。我将您的代码敲成可运行的形状 - gist.github.com/914504 - 并使用 OpenJPA 2.0.1 运行它(我重命名了这些类,因为不管你信不信,OpenJPA 阻塞了一个名为“B”的类!)。版本列不会随着重复运行而增加。您使用的是什么提供商?你能和你的供应商一起运行这个小应用程序,看看会发生什么吗?
  • I can't reproduce this. 从那以后我也没有。你的代码完美无瑕……我不得不稍微修改一下我的数据库布局,在那里添加代码,在那里删除一些代码……然后它就消失了。 :/我有预感是什么问题。我的表没有使用唯一列作为 id,而是使用自动生成的键。因此我不能使用EntityManager.find(Object),我不得不在唯一字段上编写查询。而且我认为在成功查询后,Hibernate(我的提供者)会增加查询实体的版本。我想不出别的了。
  • 这就是程序员的生活!不过,我想说的是,当您发布一个简化的示例时,值得检查它是否确实重现了问题 - 因为如果没有,您已经朝着解决方案迈出了一大步。当然,根据您的示例工作的其他任何人都无法帮助您!
  • 是的,你是对的,但我简直不敢相信执行查询会增加版本字段......也许不是。 :) 我不肯定。有时间我会检查它并更新我的问题。

标签: java database jpa persistence jpa-2.0


【解决方案1】:
 B b = em.find(B.class, c.getBId()); // a `B` generally doesn't exist in      
 if (null == a) {  

我想你在发布这两行代码时犯了一个小错误,if 条件中的变量应该是 b 而不是 a。如果是这种情况,下面的行会导致更新版本字段。

 a.bs.add(b); 

【讨论】:

  • 确实 null == anull == b,谢谢。但是,如果在持久性上下文中找到b,则a.bs.add(b) 永远不会被执行,因此对应于a 的版本字段不应该得到更新(至少在我的书中)。跨度>
  • 好吧,当 b 被发现时你是正确的,a 没有更新。但是当 b 找不到时,则创建一个新的 B 实体,它与找到的 a 相关联。跨度>
  • 没错,但问题是我的数据库中的版本列总是无论发生什么都会更新,即使找到b 并且EntityManager.persist(Object) 永远不会被调用。我想知道为什么会发生这种情况以及我能做些什么。
【解决方案2】:

我认为这里发生的情况是您将 lock mode 设置为 OPTIMISTIC_FORCE_INCREMENT。如果您将其设置为纯 OPTIMISTIC,那么您将看不到更新。该行为的 javadoc 解释是精确但相当密集的;还有一个blog post 可能会有所帮助。

我认为 OPTIMISTIC_FORCE_INCREMENT 的意义在于它保护了数据库的一致性。想象一下,你有两个 A,a1 和 a2,每个都有一个 B。两个线程同时加载它们。线程 1 将 a1 设置为具有两个 B,将 a2 设置为具有单个 B(现状)。线程 2 将 a1 设置为具有一个 B(现状),将 a2 设置为具有两个 B。线程 1 在线程 2 之前提交。使用 OPTIMISTIC_FORCE_INCREMENT,线程 2 将获得异常,因为对象已经被线程 1 修改。使用 OPTIMISTIC,线程 2 会成功,因为每个线程只会碰撞它正在添加的对象的版本一个 B 到。如果线程 2 成功,数据库现在表示两个线程都没有提交的状态 - a1 和 a2 都有两个 B。

现在,这种不一致(一种状态的隐式合并)可能是可以接受的,而且确实是您想要的。但是 JPA 通常不能假设,所以 OPTIMISTIC_FORCE_INCREMENT 是可用的。我不知道它是否是默认设置(我找不到任何这样说的文档,但它是明智的),或者您是否在其他地方明确设置它。

至于解决方案,在根本没有修改任何内容的情况下,您可以简单地不提交会话。如果您正在处理一些对象被修改而一些没有被修改的情况,您可以在其自己的会话中处理每个 A 对象。或者你可以只使用 OPTIMISISTIC 锁定。如果我是对的,那就是!

【讨论】:

  • 我尝试在EntityManager.find(...) 中使用LockModeType.OPTIMISTIC 无济于事。版本已更新。但是我的主要问题是,如果我不更改行的状态并且不坚持任何东西,只需提交我的行上的版本仍然会更新。 As for solutions, in the case where nothing at all is modified, you could simply not commit the session. 是的,这行不通,因为通常每次都会创建行,我不能忽略这些。你在推理乐观锁定是正确的,但这不是我的问题。
  • 一段时间后,我已经查看了我的问题以及您的答案。我无法重现我当时所面临的问题,因为我热爱我的生命。但是,明确使用 EntityManager.find(..., LockModeType.OPTIMISTIC_FORCE_INCREMENT) 会重现问题,但我不知道我是如何设法使用它的(因为我没有使用无锁定,从来没有人可以证明它!: )。一种可能性是我使用的 Hibernate 版本(可能是 beta 版本,我不确定)默认使用 that ...无论如何,你的答案非常好,所以我我接受它!干杯!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-11
  • 2012-03-01
  • 1970-01-01
  • 2016-09-17
  • 2021-04-16
相关资源
最近更新 更多