先来看一个例子,我们先发起请求/testA,随后立即发起请求/testB:
@Transactional
@PostMapping("/testA")
public void testA() throws InterruptedException {
// 请求a 第一次查询 原值为100
SellerCreditDO testDO = testMapper.findById(TEST_ID);
System.out.println("request a ==> before change : " + testDO.getCreditScore());
// sleep期间发起请求b
Thread.sleep(5000L);
// 请求a 第二次查询
testDO = testMapper.findById(TEST_ID);
System.out.println("request a ==> after change : " + testDO.getCreditScore());
}
@PostMapping("/testB")
public void testB() {
// 请求a第一次查询执行后发起请求b更新对应值值为90
SellerCreditDO testDO = testMapper.findById(TEST_ID);
testDO.setCreditScore(90);
testMapper.update(testDO);
System.out.println("request b => change value to 90 -----");
}
由于spring默认使用数据库的隔离级别,而阿里云rds默认使用的隔离级别为read committed,因此这里使用的是rc隔离级别,根据我们对隔离级别的认识,这里的输出应该为:
request a ==> before change : 100
request b => change value to 90 -----
request a ==> after change : 90
但是实际上,这里的输出为:
request a ==> before change : 100
request b => change value to 90 -----
request a ==> after change : 100
这里是否是因为spring使用的隔离级别不是read committed?我们来尝试做几个修改:
- 我们将mybatis一级缓存的scope设置为statement(mybatis一级缓存默认开启,为session级别。):
configuration.setLocalCacheScope(LocalCacheScope.STATEMENT);
再次测试,我们发现得到了预期的输出:
request a ==> before change : 100
request b => change value to 90 -----
request a ==> after change : 90
- 不修改mybatis一级缓存的scope,去掉testA方法上的@Transaction注解
再次测试,发现也能得到预期的输出。
有了上面的案例,经过分析框架代码可以总结如下:
- mybatis一级缓存默认开启,是sqlSession级别的缓存,在同一个sqlSession下,对相同条件的sql查询结果会进行缓存。
- sqlSession调用flush或者close之后,会清理mybatis一级缓存;
session内发生 insert、update 和 delete 操作时,会清空缓存;
一个session内发生的insert、update 和 delete 操作,不会影响其他session内的一级缓存。 - 在spring集成mybatis时,如果不开启事务,spring对于每次查询会使用不同的sqlSession,因此mybatis一级缓存是不生效的(每次查询都是一个单独的事务);
如果开启事务,spring在事务内会使用同一个sqlSession进行查询,这个时候mybatis一级缓存是生效的,而这个时候,在某些场景下我们只根据隔离级别作出的判断可能就不对了,需要注意。
建议:一般不要使用mybatis提供的缓存,将一级缓存设置为statement级别。