今天使用@Transactional遇到了问题,通过一些实验,总结下自己的想法。如有不当欢迎指正~
项目使用spring boot + mybatis + mysql,先上代码:
@Override
@Transactional
public void test(){
UDeptDO dept = uDeptDao.get("0cf58a5d6ae811e89feafa163eb3e537");
// 打印旧值
log.info("deptname={}", dept.getDeptname());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
dept.setDeptname("test");
update(dept);
dept = uDeptDao.get("0cf58a5d6ae811e89feafa163eb3e537");
// 打印新值
log.info("deptname={}", dept.getDeptname());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public void test1(){
UDeptDO dept = uDeptDao.get("0cf58a5d6ae811e89feafa163eb3e537");
// 打印旧值
log.info("deptname={}", dept.getDeptname());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
// 打印旧值,事物未提交
dept = uDeptDao.get("0cf58a5d6ae811e89feafa163eb3e537");
log.info("deptname={}", dept.getDeptname());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
// 打印新值,事物已提交,READ_COMMITTED生效
dept = uDeptDao.get("0cf58a5d6ae811e89feafa163eb3e537");
log.info("deptname={}", dept.getDeptname());
}
以上是一个service实现类的两个测试方法,方法test使用Spring事物的默认隔离级别(也就是由底层数据库提供的隔离级别,mysql默认是REPEATABLE_READ),方法test1使用READ_COMMITTED。同时调用test和test1,预期test1方法第三次查询能获取到修改值,但是并没有成功(此处纠结了好久,还以为自己理解错了。。。)。分析日志发现test1后两次查询未打印SQL语句,估计是缓存的原因。给mapper文件的查询语句加上useCache="false" flushCache="true"这两个配置,问题解决。
从这个例子可以看到,READ_COMMITTED可以避免dirty reads,即test1第二次查询不会查到test的修改,只有test方法事物提交后,test1在第三次查询才能看到。这里test1方法后两次的查询行为,其实就是我们常说的non-repeatable reads。如果使用REPEATABLE_READ级别,能使test1的三次查询结果保持一致。