一级缓存和二级缓存
- 默认情况下, 只有一级缓存(SqlSession级别的缓存,也称本地缓存)开启
- 二级缓存需要动手开启和配置,它是基于namesapce级别的缓存
- 为了提高扩展性,mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存
public void testFirstLevelCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessnionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessnionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpById(1);
System.out.println(emp);
Employee empp = mapper.getEmpById(1);
System.out.println(emp==empp);
}
//Employee [id=1, lastName=Cao, email=john@.com, gender=1, dept=null]
//true
这是上述代码的结果 : 我们执行了两次都获取的是 id 为 1 的员工资料 , emp==empp 的返回值为true 也就是说在第二次执行的时候并没有去重新封装一个对象并返回而是因为查询的是同样一个对象,所以返回的是一级缓存里的之前查询过的对象。
这也就证实了一级缓存的存在
一级缓存失效的四种情况
1. 两次查询使用的是不同的SqlSession
public void testFirstLevelCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessnionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessnionFactory.openSession();
SqlSession sqlSession2 = sqlSessnionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpById(1);
System.out.println(emp);
Employee empp = mapper2.getEmpById(1);
System.out.println(emp==empp);
}
//Employee [id=1, lastName=Cao, email=john@.com, gender=1, dept=null]
//false
由上面代码结果来看可以知道如果使用不同的sqlsession 的话 那么返回到的将不再是同一个对象
因为一个sqlSession对应一个一级缓存,而在这里使用的是不同的SqlSession,那么第二个SqlSeesion 访问不到第一个SqlSession缓存,也就产生的一级缓存失效
一级缓存里不含有当前查询内容
在使用同一sqlSession查询的时候,如果之前没查询过,那么就要重新去访问数据库,查询数据,也就相当于之前的缓存失效
同一个SqlSession 但是两次查询之间执行了增删改
public void testFirstLevelCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessnionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessnionFactory.openSession();
// SqlSession sqlSession2 = sqlSessnionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
// EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpById(1);
mapper.addEmp(new Employee(null,"li","li@.com","1",null));
System.out.println(emp);
Employee empp = mapper.getEmpById(1);
System.out.println(empp);
System.out.println(emp==empp);
}
//Employee [id=1, lastName=Cao, email=john@.com, gender=1, dept=null]
//Employee [id=1, lastName=Cao, email=john@.com, gender=1, dept=null]
//false
因为存在两次查询之间进行了增删改的操作,这样的操作可能会让数据库的内容会产生变化,所以在第二次查询的时候,会重新想数据库发送请求,而不是访问缓存
在两次查询之间进行了缓存清空
也就是在两次查询之间进行了openSession.clearCache() 操作,缓存清空自然不回去访问缓存,二十访问数
二级缓存
/**
* mybaits 拥有两级缓存
*
* 一级缓存 (本地缓存) : sqlSession级别的缓存,一级缓存是一直开启的
* 数据库同一次会话期间查询到的数据会放在本地缓存中
* 以后需要获取相同的数据,直接从缓存中那,没必要再去数据库查询
*
* 一级缓存失效的情况 (没有使用到当前一级缓存的情况,效果就是还要想数据库发出查询请求)
*
*
* 1. sqlSession 变了 那么会出现一级缓存失效 (因为一个SqlSession 对应一个一级缓存 第二个sqlSession 用不到第一个缓存)
* 2. sqlSession 相同但是查询条件不同
* 3. 如果sqlSession 相同,如果两次查询之间执行了增删改的操作
* 4. 同一个sql但是进行了缓存清空的操作
*
* 二级缓存 (全局缓存):是基于namespace级别的缓存, 可以理解为一个namespace对应一个二级缓存
* 1、 查询一条数据:这个数据就会被放在当前会话的一级缓存中
* 2、 如果会话关闭,一缓存中的数据会被保存到二级缓存中;新的会话查询信息就可以参照新之前的会话
* 3、 sqlSession===EmployeeMapper==>Employee
* DepartmentMapper ===> Department
* 不同的namespace查出的数据会放在自己对应的缓存中 , 而缓存的储存机制就是一个map
*
* 二级缓存的使用:
* 1、 开启全局二级缓存配置 : 在全局文件的setting中添加 cacheEnabled 为true
* 2、 去mapper.xml 中配置使用: <cache></cache> 仅仅添加这一行就可以了
*
*
*
*
* eviction : 的回收策略
* LRU - 最近最少使用的: 一出长时间不被使用的对象
* FIFO - 先进先出 按对象进入缓存的顺来移除他们
* SOFT -- 软引用: 一处基于垃圾回收器状态和引用规则的对象
* WEAK --- 弱引用 : 更积极的移除基于垃圾收集器状态和弱引用规则的对象
* ---- 默认的是LRU
* 3. 因为在缓存中会使用 序列化和反序列化进行克隆缓存数据从而实现只读 所以要实现序列化接口 即给每个pojo 进行 implements Serializable
*
* 二级缓存的效果 : 数据会从二级缓存中获取
* 查出的数据都会被默认先放在以及缓存中
* 只有会话被提交或者关闭以后 以及缓存在才会转移到二级缓存中
*
*
*/
二级缓存验证
public void testSecondLevelCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
Employee emp1 = mapper.getEmpById(1);
sqlSession.close();
Employee emp2 = mapper2.getEmpById(1);
System.out.println(emp1 == emp2);
}
// 最终执行结果为 true 也就证明了两个emp是同一个对象 ,在这里要注意的是 如果在xml文件中加载缓存的时候 对readonly进行定义为false 是 则返回为 false ,因为当是false时 不进行只读操作是直接将相应的缓存对象进行 序列化和反序列化操作克隆了一个新的对象并返回,所以两个对象是不相同的,但是同样获取两次emp 是访问了一次数据库
其他属性
和缓存有关的设置\属性:
1) 、cacheEnabled=true false 关闭缓存(二级缓存关闭) 一级缓存是一直以使用的
2) 、 useCache="true" 每个select 标签都有这个属性 如果是false的话 关闭二级缓存一级缓存照旧使用
<select id="getEmpById" resultType="emp" useCache="true">
select * from tbl_employee where id = #{id}
</select>
3) 每个增删改标签中 : 会有flushCache="true" 增删改执行完成是清除缓存 此时一级缓存和二级缓存都会清空
4) 查询标签中 默认flushCache 是false 如果我们改为true 每次查询之前都会清空,所以缓存是没有被使用的
5) openSession.clearCache 清楚的是当前Session的一级缓存 不会清楚二级缓存
6) localCacheScope: 本地缓存作用域: (一级缓存Session) 当前会话的所有数据保存在会话缓存中,
赋值 SESSION 使用一级缓存 这个是默认的
STATEMENT 可以禁用当前一级缓存
mybatis缓存原理
sqlSession 想数据库发送请求, 数据库返回给sqlsession的缓存,当session关闭的时候,则会将缓存的内容上传的二级缓存。
缓存的使用顺序 : 二级缓存 ===> 一级缓存 ===> 数据库
而在缓存内部的存储其实就是一个map集合