lock.lock
介绍
引入了Hibernate 显式锁定支持以及Cascade Types之后 ,就该分析CascadeType.LOCK行为了。
Hibernate锁定请求触发内部LockEvent 。 关联的DefaultLockEventListener可以将锁定请求级联到锁定实体子级。
由于CascadeType.ALL也包括CascadeType.LOCK ,因此当锁定请求从父级实体传播到子级实体时,值得理解。
测试时间
我们将从以下实体模型开始:
Post是PostDetail一对一关联和Comment一对多关联的Parent实体,这些关联用CascadeType.ALL标记:
@OneToMany(
cascade = CascadeType.ALL,
mappedBy = "post",
orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
@OneToOne(
cascade = CascadeType.ALL,
mappedBy = "post",
optional = false,
fetch = FetchType.LAZY)
private PostDetails details;
所有即将到来的测试用例将使用以下实体模型图:
doInTransaction(session -> {
Post post = new Post();
post.setName("Hibernate Master Class");
post.addDetails(new PostDetails());
post.addComment(new Comment("Good post!"));
post.addComment(new Comment("Nice post!"));
session.persist(post);
});
锁定管理实体
将受管实体加载到当前正在运行的持久性上下文中,并将所有实体状态更改转换为DML语句。
当托管父实体被锁定时:
doInTransaction(session -> {
Post post = (Post) session.createQuery(
"select p " +
"from Post p " +
"join fetch p.details " +
"where " +
" p.id = :id")
.setParameter("id", 1L)
.uniqueResult();
session.buildLockRequest(
new LockOptions(LockMode.PESSIMISTIC_WRITE))
.lock(post);
});
只有父实体被锁定,因此可以防止级联:
select id from Post where id = 1 for update
Hibernate定义了一个范围 LockOption ,该范围 (根据JavaDocs)应允许将锁定请求传播到Child实体:
“范围”是JPA定义的术语。 基本上,它是关联锁定的级联。
session.buildLockRequest(
new LockOptions(LockMode.PESSIMISTIC_WRITE))
.setScope(true)
.lock(post);
设置范围标志不会改变任何东西,只有被管实体被锁定:
select id from Post where id = 1 for update
锁定独立实体
除了实体锁定之外,锁定请求还可以重新关联分离的实体。 为了证明这一点,我们将在锁定实体请求之前和之后检查Post实体图:
void containsPost(Session session,
Post post, boolean expected) {
assertEquals(expected,
session.contains(post));
assertEquals(expected,
session.contains(post.getDetails()));
for(Comment comment : post.getComments()) {
assertEquals(expected,
session.contains(comment));
}
}
以下测试演示了CascadeType.LOCK如何用于分离的实体:
//Load the Post entity, which will become detached
Post post = doInTransaction(session ->
(Post) session.createQuery(
"select p " +
"from Post p " +
"join fetch p.details " +
"join fetch p.comments " +
"where " +
" p.id = :id")
.setParameter("id", 1L)
.uniqueResult());
//Change the detached entity state
post.setName("Hibernate Training");
doInTransaction(session -> {
//The Post entity graph is detached
containsPost(session, post, false);
//The Lock request associates
//the entity graph and locks the requested entity
session.buildLockRequest(
new LockOptions(LockMode.PESSIMISTIC_WRITE))
.lock(post);
//Hibernate doesn't know if the entity is dirty
assertEquals("Hibernate Training",
post.getName());
//The Post entity graph is attached
containsPost(session, post, true);
});
doInTransaction(session -> {
//The detached Post entity changes have been lost
Post _post = (Post) session.get(Post.class, 1L);
assertEquals("Hibernate Master Class",
_post.getName());
});
锁定请求重新关联了实体图,但是当前正在运行的Hibernate Session并未意识到实体处于分离状态时变脏了。 仅在不强制执行UPDATE或选择当前数据库状态进行进一步比较的情况下,才重新连接实体。
一旦对实体进行管理, 脏检查机制将检测到任何进一步的更改,并且刷新还将传播重新附加的更改。 如果在管理实体时未发生任何更改,则不会安排该实体进行刷新。
如果我们要确保分离的实体状态始终与数据库同步,则需要使用merge或update 。
当scope选项设置为true时,分离的实体传播lock选项:
session.buildLockRequest(
new LockOptions(LockMode.PESSIMISTIC_WRITE))
.setScope(true)
.lock(post);
Post实体锁定事件会传播到所有Child实体(因为我们正在使用CascadeType.ALL ):
select id from Comment where id = 1 for update
select id from Comment where id = 2 for update
select id from PostDetails where id = 1 for update
select id from Post where id = 1 for update
结论
锁级联不是直截了当或直观的。 明确锁定需要勤奋(越锁我们获得了,死锁的几率就越大),你最好还是保留对儿童实体锁传播完全控制反正。 类似于并发编程的最佳实践,因此,手动锁定优于自动锁定传播。
- 代码可在GitHub上获得 。
翻译自: https://www.javacodegeeks.com/2015/03/hibernate-cascadetype-lock-gotchas.html
lock.lock