假设您有多个用户使用的系统,其中每个用户都试图同时修改同一实体。当并发访问时,如何确保数据的完整性?
持久性提供了一种锁的策略来管理并发。锁有两种类型:乐观锁和悲观锁。在深入研究锁策略之前,让我们学习一下ACID事务。
ACID(原子性,一致性,隔离性,持久性)事务可确保数据库事务及时完成。关系数据库(例如MySQL,Postgres,SQL Server和Oracle)都符合ACID。
数据库事务可以分解为多个组件。仅当事务的所有组件都成功时,符合ACID的数据库才能确保事务得以持久和落实。如果任何一个组件发生故障,则事务将回滚,并且不会进行任何更改。
如果有多个事务,则需要定义锁策略,以确保数据的完整性。
让我们看看本文中的每种锁定策略。
乐观锁
JPA支持javax.persistence.Version
注解,将属性标记为实体的版本属性。此属性可以是数字(int,long,short)或java.sql.Timestamp字段。每个实体只能存在一个带有@Version
注解的属性。
此外,对于映射到多个表的实体,@Version
属性必须在主表中。
1 | @Entity |
在这里,每个读取数据的事务都拥有version
属性的值。在任何修改数据的事务之前,它将再次检查version
属性的值。
如果此时值发生更改,则将引发OptimisticLockException
并回滚事务。否则,事务将提交更新并增加version
属性的值。
乐观锁模式
在乐观锁的情况下,JPA提供了两种锁模式:OPTIMISTIC
和OPTIMISTIC_FORCE_INCREMENT
。OPTIMISTIC获得有@Version
属性的实体的读取锁。OPTIMISTIC_FORCE_INCREMENT
为有@Version
属性的实体获取读取锁,并增加该属性的值。
1 | @Service |
悲观锁
在悲观锁的情况下,JPA创建一个事务,该事务获得数据的锁,直到事务完成为止。这样可以防止在释放锁之前其他事务对实体进行任何更新。
当数据经常被多个事务访问和修改时,悲观锁定非常有用。
请记住,如果实体不易于频繁修改,则使用悲观锁定可能会导致应用性能下降。
悲观锁模式
JPA提供了三种悲观锁模式:PESSIMISTIC_READ
、PESSIMISTIC_FORCE_INCREMENT
和 PESSIMISTIC_WRITE
。
PESSIMISTIC_READ 获得数据的共享锁,防止更新或删除数据。其他事务可能会在锁定期间读取数据,但将无法修改或删除数据。
PESSIMISTIC_WRITE 获得数据的排他锁,防止读取、更新或删除数据。
PESSIMISTIC_FORCE_INCREMENT 和PESSIMISTIC_WRITE一样,另外增加@Version属性的值。
1 | User user = entityManager.find(User.class,id); |
如果无法获得悲观锁,则将导致事务回滚,并且将引发PessimisticLockException
。但是,如果锁定数据失败不会导致事务回滚,则将抛出LockTimeoutException
。
结论
根据您的应用需求,选择正确的锁定策略并仅在必要时应用锁定策略。