lock.lock_HibernateCascadeType.LOCK陷阱

lock.lock

介绍

引入了Hibernate 显式锁定支持以及Cascade Types之后 ,就该分析CascadeType.LOCK行为了。

Hibernate锁定请求触发内部LockEvent 。 关联的DefaultLockEventListener可以将锁定请求级联到锁定实体子级。

由于CascadeType.ALL也包括CascadeType.LOCK ,因此当锁定请求从父级实体传播到子级实体时,值得理解。

测试时间

我们将从以下实体模型开始:

注释后详细信息级联

PostPostDetail一对一关联和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或选择当前数据库状态进行进一步比较的情况下,才重新连接实体。

一旦对实体进行管理, 脏检查机制将检测到任何进一步的更改,并且刷新还将传播重新附加的更改。 如果在管理实体时未发生任何更改,则不会安排该实体进行刷新。

如果我们要确保分离的实体状态始终与数据库同步,则需要使用mergeupdate

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

结论

锁级联不是直截了当或直观的。 明确锁定需要勤奋(越锁我们获得了,死锁的几率就越大),你最好还是保留对儿童实体锁传播完全控制反正。 类似于并发编程的最佳实践,因此,手动锁定优于自动锁定传播。

翻译自: https://www.javacodegeeks.com/2015/03/hibernate-cascadetype-lock-gotchas.html

lock.lock

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是填空后的代码: ```python import threading import time # 定义线程锁对象 lock = threading.Lock() # 定义计数器对象 class Counter: def __init__(self): self.data = 0 # 自定义线程类 class MyThread(threading.Thread): def __init__(self, counter): # 调用父类构造函数 threading.Thread.__init__(self) self.counter = counter #定义线程的操作函数 def run(self): #获取一个锁资源 lock.acquire() print("新线程操作开始...") self.counter.data += 1 #当前线程休眠3秒 time.sleep(3) self.counter.data += 1 print("新线程操作结束...") # 写线程执行完毕,释放锁资源 lock.release() # 主函数 if __name__ == "__main__": # 创建计数器对象 counter = Counter() # 创建新线程对象 new_thread = MyThread(counter) #启动新线程 new_thread.start() #等待新线程运行完毕 new_thread.join() print("新线程运行完毕,数据值:", counter.data) ``` 在这段代码,我们首先定义了一个线程锁对象 `lock`,并初始化计数器对象 `Counter`。 然后,我们自定义了一个线程类 `MyThread`,该类继承自 `threading.Thread` 类,并重写了 `run()` 方法,用于实现线程的操作。 在 `run()` 方法,我们首先使用 `lock.acquire()` 方法获取一个锁资源,然后执行对共享数据的访问操作。在操作完成后,我们使用 `lock.release()` 方法释放锁资源。 在主函数,我们创建了一个新线程对象 `new_thread`,并调用 `start()` 方法启动新线程。然后,我们使用 `join()` 方法等待新线程运行完毕。最后,我们输出计数器对象的数据值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值