首先要知道:sqlsession(接口)的底层是HashMap储存,线程不安全,sqlsessionTemplate是其实现类,线程是安全的
一级缓存介绍
在程序运行过程中,可能会在与数据库的会话中,多次查询一条相同的sql,Mybatis提供了一级缓存的方案优化这部分场景,如果是相同的sql会优先命中一级缓存,避免对数据库的直接查询,提高了性能。具体的执行过程图如下:
每个SqlSession中都持有Executor,每个Executor中都有Local Cache。当程序执行查询的时候,Mybatis根据当前执行的语句生成'MappedStatement',在Local Cache中进行查询,如果缓存命中直接将结果返回给用户,反之会先去数据库查询,结果写入Local Cache中,最后将结果返回给用户。
一级缓存实验
接下来通过实验来了解Mybatis的一级缓存的效果。
首先是创建实例表customer
create table customer
(
cid int auto_increment comment '用户id',
username varchar(4) null comment '用户名称',
age int null comment '年龄',
constraint customer_pk
primary key (cid)
)
comment '用户表';
并添加一条数据
实验1
开启一级缓存,范围为会话级别,调用三次queryCustomerByCid
,代码示例如下:
import com.zhao.mybatiscache.mapper.L1CacheMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "L1CacheController")
public class L1CacheController {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@PostMapping(value = "queryCustomerByCid")
public void queryCustomerByCid() {
SqlSession sqlSession = sqlSessionFactory.openSession();
L1CacheMapper mapper = sqlSession.getMapper(L1CacheMapper.class);
System.out.println(mapper.queryCustomerByCid(1));
System.out.println(mapper.queryCustomerByCid(1));
System.out.println(mapper.queryCustomerByCid(1));
}
}
执行结果:
可以看到,在执行相同的查询三次,只有第一次真正的查询了数据库,后续的查询使用了一级缓存,从缓存中获取数据
实验2
增加了对数据库的修改操作,验证再一次会话中,对数据库进行一次修改,一级缓存是否失效,代码示例如下:
@PostMapping(value = "addCustomer")
public void addCustomer() {
SqlSession sqlSession = sqlSessionFactory.openSession();
L1CacheMapper mapper = sqlSession.getMapper(L1CacheMapper.class);
System.out.println(mapper.queryCustomerByCid(1));
System.out.println("增加了" + mapper.addCustomer(new Customer().setUsername("张三").setAge(18)) + "个用户");
System.out.println(mapper.queryCustomerByCid(1));
}
执行结果:
可以看见,程序第一次执行了查询操作后,添加了一条数据,再次执行查询操作,是从数据库中查询了数据,没有命中缓存,一级缓存失效
实验3
开启两个SqlSession
,在SqlSession1
中查询数据,使一级缓存生效,再在SqlSession2
中更新数据库,验证一级缓存只在数据库回话内部共享,代码示例如下:
@PostMapping(value = "localCacheScope")
public void localCacheScope() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
L1CacheMapper mapper1 = sqlSession1.getMapper(L1CacheMapper.class);
System.out.println("sqlSession1读取数据" + mapper1.queryCustomerByCid(1));
System.out.println("sqlSession1读取数据" + mapper1.queryCustomerByCid(1));
SqlSession sqlSession2 = sqlSessionFactory.openSession();
L1CacheMapper mapper2 = sqlSession2.getMapper(L1CacheMapper.class);
System.out.println("sqlSession2更新了" + mapper2.updateCustomer(new Customer().setUsername("张三").setCid(1)) + "个用户");
System.out.println("sqlSession1读取数据" + mapper1.queryCustomerByCid(1));
System.out.println("sqlSession1读取数据" + mapper1.queryCustomerByCid(1));
}
可以看见,SqlSession2更新了id为1的用户,将“赵毅梵”更新成了“张三”,但是SqlSession1之后的查询还是命中了一级缓存,缓存中出现了脏数据,也证明了以前的猜想,一级缓存只会在会话内部共享。
一级缓存工作流程&源码分析
工作流程
源码解析
这个先欠着
总结
- Mybatis一级缓存的生命周期和SqlSession一致。
- Mybatis一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的情况下,数据库写操作会引起脏数据,建议设定缓存为
Statement
。
网友评论