JPA 和乐观锁定模式
Posted
技术标签:
【中文标题】JPA 和乐观锁定模式【英文标题】:JPA and optimistic locking modes 【发布时间】:2012-11-14 22:03:03 【问题描述】:我在 Oracle here 的博客上阅读了一篇关于 JPA 和锁定模式的文章。
我不完全理解OPTIMISTIC
和OPTIMISTIC_FORCE_INCREMENT
锁定模式类型之间的区别。
OPTIMISTIC
模式:
当用户使用此模式锁定实体时,会在事务开始时检查版本字段实体 (@version
),并在事务结束时检查版本字段。如果版本不同,则事务回滚。
OPTIMISTIC_FORCE_INCREMENT
模式:
当用户选择此模式时,他必须将 EntityManager 的状态刷新()到数据库中以手动增加版本字段。因此,所有其他乐观事务都将失效(回滚)。在事务结束时也会检查版本以提交或回滚事务。
似乎很清楚,但我应该何时使用 OPTIMISTIC
与 OPTIMISTIC_FORCE_INCREMENT
模式?我看到的唯一标准是当我希望事务优先于其他事务时应用 OPTIMISTIC_FORCE_INCREMENT
模式,因为选择此模式将回滚所有其他正在运行的事务(如果我理解机制的话)。
除了OPTIMISTIC
模式,还有其他理由选择这种模式吗?
谢谢
【问题讨论】:
我认为我们使用 OPTIMISTIC_FORCE_INCREMENT 来更新包含另一种类型的实体作为字段(@oneToOne,@oneToMany)的实体。因此,当我们想要更新 List 时,我们使用 OPTIMISTIC_FORCE_INCREMENT 锁定所有 ListItem。因此,在提交时,我们确保与我们要更新的实体链接的所有实体都保持不变。不是吗? 【参考方案1】:不要被这个冗长的答案吓到。这个话题不简单。
默认情况下 JPA 强加
如果您未指定任何锁定,读取已提交隔离级别(与使用 LockModeType.NONE
的行为相同)。
已提交读要求脏读现象不存在。只是 T1 只能在 T2 提交后看到 T2 所做的更改。
在 JPA 中使用乐观锁定将隔离级别提高到 可重复读取。
如果 T1 在事务开始和结束时读取一些数据,可重复读取确保 T1 看到相同的数据,即使 T2 更改数据并在 T1 中间提交。
这里是棘手的部分。 JPA 以最简单的方式实现可重复读取:通过防止 不可重复读取现象。 JPA 不够复杂,无法保留读取的快照。它只是通过引发异常来防止发生第二次读取(如果数据与第一次读取相比已更改)。
您可以从两个乐观锁定选项中进行选择:
LockModeType.OPTIMISTIC
(JPA 1.0 中的LockModeType.READ
)
LockModeType.OPTIMISTIC_FORCE_INCREMENT
(JPA 1.0 中的LockModeType.WRITE
)
两者有什么区别?
让我用这个Person
实体的例子来说明。
@Entity
public class Person
@Id int id;
@Version int version;
String name;
String label;
@OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
List<Car> cars;
// getters & setters
现在假设我们有一个名为 John 的人存储在数据库中。我们在 T1 中读取了这个人,但在第二次交易 T2 中将他的名字改为 Mike。
没有任何锁定
Person person1 = em1.find(Person.class, id, LockModeType.NONE); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(em1.find(Person.class, id).getName()); // prints "John" - entity is already in Persistence cache
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // prints 0 - ups! don't know about any John (Non-repetable read)
乐观读锁
Person person1 = em1.find(Person.class, id, LockModeType.OPTIMISTIC); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // OptimisticLockException - The object [Person@2ac6f054] cannot be updated because it has changed or been deleted since it was last read.
LockModeType.OPTIMISTIC_FORCE_INCREMENT
用于对其他实体进行更改(可能是非拥有关系)并且我们希望保持完整性。
让我以约翰购买一辆新车为例。
乐观读锁
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. We'll end up with wrong info about John.
if (john1.getCars().size() > 0)
john1.setLabel("John has a car");
else
john1.setLabel("John doesn't have a car");
em1.flush();
乐观写锁
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC_FORCE_INCREMENT); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. That's ok though because proper locking won't let us save wrong information about John.
if (john1.getCars().size() > 0)
john1.setLabel("John has a car");
else
john1.setLabel("John doesn't have a car");
em1.flush(); // OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
虽然 JPA 规范中有如下注释,但 Hibernate 和 EclipseLink 表现不错,不要使用它。
对于版本化对象,允许实现使用 LockMode- Type.OPTIMISTIC_FORCE_INCREMENT 其中 LockModeType.OPTIMISTIC 被请求, 但反之亦然。
【讨论】:
我认为在您的最后一个示例中,当您因为没有使用锁而执行 em1.flush() 时,我们应该不会遇到异常。实际上,因为您在实体中使用了版本:***.com/a/19872657/1059372 我针对 Hibernate 4.3.6.Final 和 Eclipselink 2.5.1 测试了我的示例。关于您的担忧:在 cmets 中可以看到休眠消息。 Eclipselink 打印 OptimisticLockException The object [Person@716d7daf] cannot be updated because it has changed or deleted since it was last read.Person
确实如第一次截断所示被版本化。
我检查了在Person
上没有@Vesion
列的情况下它会如何表现,它会在锁定时失败。带有 AssertionFailure 的休眠:无法强制在非版本化实体上增加版本 和带有 PersistenceException 的 Eclipselink:对于没有版本锁定索引的实体,锁定模式类型无效。没有版本锁定索引时,只能使用 PESSIMISTIC 锁定模式类型。
@zbig 嘿,你提到了乐观读锁的两个例子,在第一个例子中,em1 发生了 OptimisticLockException,因为 em2 已经更改了对象。但是,在第二个示例中,即使 em2 更改对象,您也没有提到 em1 的任何异常。请澄清..
“LockModeType.OPTIMISTIC”是否要求实体中存在版本列?【参考方案2】:
通常您永远不会使用 lock() API 进行乐观锁定。 JPA 将在任何更新或删除时自动检查任何版本列。
lock() API 用于乐观锁定的唯一目的是当您的更新依赖于另一个未更改/更新的对象时。如果其他对象发生更改,这允许您的事务仍然失败。
何时执行此操作取决于应用程序和用例。 OPTIMISTIC 将确保在您提交时其他对象尚未更新。 OPTIMISTIC_FORCE_INCREMENT 将确保其他对象尚未更新,并会在提交时增加其版本。
乐观锁定总是在提交时验证,并且在提交之前不能保证成功。您可以使用 flush() 提前强制数据库锁定,或触发更早的错误。
【讨论】:
NONE(我认为的默认模式)和 OPTIMISTIC 之间有什么区别?谢谢 flush() 是否保证在提交之前以对其他事务可见的方式递增版本?【参考方案3】:LockModeType.OPTIMISTIC
存在check-then-act
问题,因此最好将其与PESSIMISTIC_READ
或PESSIMISTIC_WRITE
耦合。
另一方面,LockModeType.OPTIMISTIC_FORCE_INCREMENT
没有任何数据不一致问题,您通常会在修改子实体时使用它来控制父实体的版本。
例如,您可以使用LockModeType.OPTIMISTIC_FORCE_INCREMENT
或LockModeType.PESSIMISTIC_FORCE_INCREMENT
,以便parent entity version tales into consideration child entity changes as well。
【讨论】:
以上是关于JPA 和乐观锁定模式的主要内容,如果未能解决你的问题,请参考以下文章
JPA 在带有 DTO 和乐观锁定的 RESTful Web 应用程序中合并?