Spring Data JPA 对共享锁和独占锁的支持
简介
本文将会带你了解 Spring Data JPA 对锁的支持,以及如何在检索时使用共享锁或者独占锁。
JPA LockModeType
JPA 提供了几种加锁的选项,可以在获取实体时通过 LockModeType
枚举来指定。
如下:
LockModeType.PESSIMISTIC_READ
可对相关表记录加共享锁或读锁。LockModeType.PESSIMISTIC_WRITE
可对相关表记录加速独占锁或写锁。
如果底层数据库不支持共享锁,那么 PESSIMISTIC_READ
策略将退回到 PESSIMISTIC_WRITE
,因为该选项已被广泛支持。
根据 ID 获取实体时加锁
JPA EntityManager
提供了一个 find
方法的重载,可以传递 LockModeType
采参数。用于在获取实体时上锁:
Post post = entityManager.find(Post.class, id, lockMode);
Spring Data JpaRepository
未提供此选项,但我们可以使用自定义 Spring Data Repository 来实现。
例如,可以让 PostRepository
继承 JpaRepository
,CustomPostRepository
:
@Repository
public interface PostRepository
extends JpaRepository<Post, Long>, CustomPostRepository {
}
In the CustomPostRepository interface, we can define a lockById method like this:
在 CustomPostRepository
接口中,可以这样定义 lockById
方法:
public interface CustomPostRepository {
Post lockById(Long id, LockModeType lockMode);
}
Spring Data JPA 可以为查询方法或使用 @Query
注解的方法提供实现,而对于我们的 CustomPostRepository
接口,我们必须提供 CustomPostRepositoryImpl
实现,Spring Data JPA 在运行时创建 PostRepository
Java 对象实例时将使用该实现。
CustomPostRepositoryImpl
如下:
public class CustomPostRepositoryImpl
implements CustomPostRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public Post lockById(Long id, LockModeType lockMode) {
return entityManager.find(Post.class, id, lockMode);
}
}
实现了 lockById
方法后,进行测试:
在 PostgreSQL
数据库上加共享锁,必须使用 LockModeType.PESSIMISTIC_READ
:
Post postWithSharedLock = postRepository.lockById(
1L,
LockModeType.PESSIMISTIC_READ
);
然后,Hibernate 将执行以下 SQL 查询:
SELECT
p.id AS id1_0_0_,
p.slug AS slug2_0_0_,
p.title AS title3_0_0_
FROM
post p
WHERE
p.id = 1
FOR SHARE
要获取独占锁,可以s使用 PESSIMISTIC_WRITE
选项:
Post postWithExclusiveLock = postRepository.lockById(
2L,
LockModeType.PESSIMISTIC_WRITE
);
Hibernate 会使用 FOR UPDATE
子句来代替:
SELECT
p.id AS id1_0_0_,
p.slug AS slug2_0_0_,
p.title AS title3_0_0_
FROM
post p
WHERE
p.id = 2
FOR UPDATE
查询实体记录时加锁
还可以对给定 SQL 查询返回的所有记录加行级锁,为此 Spring Data JPA 提供了 @Lock
注解。
例如,在 PostCommentRepository
中,可以定义 lockAllByPostId
方法,对与给定 Post
父记录相关联的所有子 PostComment
行应用读取锁:
@Repository
public interface PostCommentRepository
extends JpaRepository<PostComment, Long> {
@Query("""
select pc
from PostComment pc
where pc.post.id = :postId
""")
@Lock(LockModeType.PESSIMISTIC_READ)
List<PostComment> lockAllByPostId(
@Param("postId") Long postId
);
}
执行 lockAllByPostId
方法:
List<PostComment> commentWithLock = postCommentRepository.lockAllByPostId(1L);
Hibernate 将在 PostgreSQL 上运行以下 SQL 查询:
SELECT
pc.id as id1_1_,
pc.post_id as post_id3_1_,
pc.review as review2_1_
FROM
post_comment pc
WHERE
pc.post_id = 1
FOR SHARE OF pc
参考:https://vladmihalcea.com/spring-data-jpa-locking/