悲观锁
准备工作
本文所讨论的悲观锁,并非普通查询的悲观锁,而是保存指令内部的悲观锁。
面对一个保存操作,在不同的情况下,保存指令会采取两种截然不同的行为:
- 
直接利用数据库本身的能力进行upsert操作 
- 
先通过select操作检查被保存的数据是否存在,再根据查询结果决定后续操作应该是insert还是update 
Jimmer会尽可能执行1,如果无法做到,则执行2并向用户报告QueryReason。
当Jimmer不得不执行2时,存在一个问题:先执行的select操作和后执行insert或update操作之间存在一个时间窗口,其他事务可能挤进这个时间窗口对相同的数据进行修改。
为了解决这个问题,保存指令允许用户启用悲观锁,在执行select操作时加上for update选项,直到事务提交为止。
在展示悲观锁之前,我们有必要让保存指令执行上面的2,而非1。一个有效的办法是启用事务内触发器:
事务内触发器是Jimmer所支持的触发器的一种,Jimmer总是通过select查询修改前的旧数据,并以此模拟出触发器的效果。
事务内触发器是当前尚未介绍的功能,读者不用太在意它,这里,我们只是用它来确保Jimmer会采用先查再改的策略而已。
采用以下任何一种方法开启事务内触发器
- 
利用Jimmer的Spring Boot Starter 修改 application.yml或application.properties文件,完成配置application.ymljimmer:
 trigger-type: TRANSACTION_ONLY
- 
使用Jimmer的核心API - Java
- Kotlin
 JSqlClient sqlClient = JSqlClient
 .newBuilder()
 .setTriggerType(TriggerType.TRANSACTION_ONLY)
 ...省略其他配置...
 .build();val sqlClient = sqlClient {
 setTriggerType(TriggerType.TRANSACTION_ONLY)
 ...省略其他配置...
 }
通过这样的配置,强迫Jimmer总是先查询再修改,以便于演示悲观锁。
悲观锁展示
我们可以通过setPessimisticLock方法为保存指令开启悲观锁
- Java
- Kotlin
List<Book> books = Arrays.asList(
    Immutables.createBook(draft -> {
        draft.setName("GraphQL in Action");
        draft.setEdition(3);
        draft.setPrice(new BigDecimal("59.9"));
    }),
    Immutables.createBook(draft -> {
        draft.setName("GraphQL in Action");
        draft.setEdition(4);
        draft.setPrice(new BigDecimal("59.9"));
    })
);
sqlClient
        .saveEntitiesCommand(books)
        .setPessimisticLock(Book.class)
        .execute();
val books = listOf(
    Book { 
        name = "SQL in Action"
        edition = 3
        price = BigDecimal("59.9")
    },
    Book {
        name = "SQL in Action"
        edition = 4
        price = BigDecimal("59.9")
    }
)
sqlClient.saveEntities(books) {
    setPessimisticLock(Book::class)
}
这里,setPessimisticLock为Book实体开启了悲观锁,如果保存指令需要查询Book类型的对象,相关select语句会启用for update选项。
事实上,你也可以调用setPessimisticLockAll方法盲目地为所有实体类型开启悲观锁
最终生成如下三条SQL
- 
查询被保存的对象是否存在,带上 for update选项Purpose: COMMAND(TRIGGER)
 SQL: select
 tb_1_.ID,
 tb_1_.NAME,
 tb_1_.EDITION,
 tb_1_.PRICE,
 tb_1_.STORE_ID
 from BOOK tb_1_
 where
 (tb_1_.NAME, tb_1_.EDITION) in (
 (? /* GraphQL in Action */, ? /* 3 */),
 (? /* GraphQL in Action */, ? /* 4 */)
 ) for update信息这里,查询语句带有 for update选项,悲观锁已经生效。
- 
插入不存在的数据 (非本文重点,默认折叠)insert into BOOK(
 NAME, EDITION, PRICE
 ) values(
 ? /* GraphQL in Action */,
 ? /* 4 */,
 ? /* 59.9 */
 )
- 
更新已经存在的数据 (非本文重点,默认折叠)update BOOK
 set
 PRICE = ? /* 59.9 */
 where
 ID = ? /* 12 */