Spring Data JPA进阶:事务和锁

563人浏览 / 0人评论

参考

https://wangchengming.blog.csdn.net/article/details/90898302

锁的使用

1、在持久层的方法上添加 @Lock 注解并选择锁的类型即可

interface UserRepository extends Repository<User, Long> {
  @Lock(LockModeType.READ)
  List<User> findByLastname(String lastname);
}

需要注意的是,要在事务里面使用锁,否则会报 TransactionRequiredException 异常。

2、在 EntityManager 中使用锁

// find的时候指定锁
entityManager.find(Department.class, 1, LockModeType.PESSIMISTIC_READ);
// find后锁住一个实体
Department department = entityManager.find(Department.class, 1);
entityManager.lock(department, LockModeType.PESSIMISTIC_READ);
// 锁住query results
TypedQuery<Department> query = entityManager
        .createQuery("select d from Department d", Department.class);
query.setLockMode(LockModeType.PESSIMISTIC_READ);
List<Department> departments = query.getResultList();

锁的类型

首先 READWRITE 是别名,NONE 是无锁,所以不作过多解释。主要介绍这五个锁:

  • OPTIMISTIC:读操作时不会更新 Version 字段的值,只有在写操作的时候会
  • OPTIMISTIC_FORCE_INCREMENT:在读和写操作时都会更新 Version 字段的值
  • PESSIMISTIC_READ
  • PESSIMISTIC_WRITE
  • PESSIMISTIC_FORCE_INCREMENT

乐观锁和悲观锁

这两种锁都可以避免两个事务中的其中一个,在不知情的情况下覆盖另一个事务的数据。以 OPTIMISTIC 开头的代表 “乐观锁”,以 PESSIMISTIC 开头的是 “悲观锁”。

乐观锁

乐观锁是在 JPA 层面实现的。顾名思义,乐观锁比较 “乐观”,它假设冲突很少发生,即使发生,抛出一个错误也比想办法避免它们更容易接受和简单。核心思想就是在实体中定义一个 version 字段,可以是数值类型或时间戳类型。在读取的时候,就取到这个字段。然后在修改了实体的数值,保存的时候,就会去数据库对比这个 version,如果 version 一致,就执行更新,否则就不执行更新。

@Entity
public class Student{
    @Id
    private int id;
    private String name;
    private BigDecimal money;
    @Version
    private int version;
    // getters and setters
}

对于实体中有添加了 @Version 注解的列,JPA 会自动对该实体使用乐观锁 OPTIMISTIC_FORCE_INCREMENT。你不需要使用任何锁命令。但是你也可以在 Repository 里面通过 @Lock 注解去自定义自己想要的锁。

使用乐观锁,打印 sql,会发现在执行 save 方法的时候,会把 version 作为条件。

也可以不使用 Spring Data JPA 的乐观锁,而自己新增一个字段,在执行更新操作的时候手动去实现。

悲观锁

悲观锁是在数据库层面实现的,JPA 只是把请求交给数据库。悲观锁比较 “悲观”,意思是只要开启事务的时候,就会对数据进行加锁操作,在事务结束后才解锁。

而有些数据库是不支持 PESSIMISTIC_READ 的,JPA 规范中说到,不需要提供 PESSIMISTIC_READ(因为许多 DB 只支持 WRITE 锁):
允许 JPA 实现用 LockModeType.PESSIMISTIC_WRITE 来代替 LockModeType.PESSIMISTIC_READ,但是反之不可。

悲观写锁 PESSIMISTIC_WRITE 是基于 SELECT … FOR UPDATE 这种 SQL 语法来实现的:

select
        id 
    from
        product 
    where
        id =? 
        and version =? for update

悲观读锁 PESSIMISTIC_READ 很多数据库不支持。在 MYSQL 中,会生成 SELECT …. LOCK INSHARE MODE

PESSIMISTIC_FORCE_INCREMENT 会使用悲观写锁,但不管有没有修改数据,都会更新 Version 字段的值。

只能在查询语句上使用锁

Spring Data JPA 目前只支持在 查询语句(SELECT语句)中使用 @Lock 注解,否则会抛异常。

全部评论