Mybatis一级缓存和二级缓存
一级缓存
一级缓存使用的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或分布式环境下,会查到过时数据的问题。如果要解决这个问题,就需要用到二级缓存。
一级缓存是基于SqlSession,SqlSession销毁或执行Update、insert、Delete会清除一级缓存。
一级缓存默认SESSION,默认是开启状态;通过localCacheScope设置STATEMENT即可关闭
二级缓存
二级缓存是用来解决一级缓存不能跨会话共享的问题,范围是namespace级别的,可以被多个SqlSession共享(只要是一个接口里面的相同方法,都可以共享),生命周期和应用同步。
二级缓存的总开关默认是开启的。但是每个Mapper的二级缓存开关默认是关闭的。一个Mapper要用到二级缓存,还需要单独打开它自己的开关。
一级缓存关闭验证
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
测试代码:
@Test
public void select() throws IOException {
String resource = "mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
System.out.println("第一次执行:"+mapper.selectBlogById(1));
System.out.println("第二次执行:"+mapper.selectBlogById(1));
}finally{
sqlSession.close();
}
}
验证结果:
一级缓存开启验证
<settings>
<setting name="localCacheScope" value="SESSION"/>
</settings>
测试代码:不变
测试结果:
二级缓存开关
1.全局开关
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
2.局部开关
<mapper namespace="com.mybatis.cache.mapper.BlogMapper">
<!-- 声明这个namespace使用二级缓存 -->
<!-- (1)<cache/> -->
<!-- (2)也可以自定义缓存配置属性,开启二级缓存二选一即可 -->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
size="1024"
eviction="LRU"
flushInterval="120000"
readOnly="false"/>
</mapper>
cache属性详解
属性 | 含义 | 取值 |
---|---|---|
type | 缓存实现类 | 需要实现Cache接口,默认是PerpetualCache,可以使用第三方缓存 |
size | 最多缓存对象个数 | 默认1024 |
eviction | 回收策略(缓存淘汰算法) | LUR - 最近最少使用;移除最长时间不被使用的对象(默认) FIFO - 先进先出;按对象进入缓存的顺序来移除它们. SOFT - 软引用: 移除基于垃圾回收器状态和软引用规则的对象. WEAK - 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象 |
flushInterval | 定时自动清空缓存间隔 | 自动刷新时间,单位ms,未配置时只有调用时的刷新 |
readOnly | 是否只读 | true: 只读缓存:会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供来很重要的性能优势。 false: 读写缓存:会返回缓存对象的拷贝(通过序列化),不会共享。这会慢一些,但是安全,因此默认是false 改为false可读写时,对象必须支持序列化. |
blocking | 启用阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于java重入锁实现 |
3.局部方法开关
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
<select id="selectList" resultMap="BaseResultMap" useCache="false">
select * from table
</select>
代码测试:
@Test
public void update() throws IOException {
String resource = "mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);
BlogMapper mapper2 = sqlSession1.getMapper(BlogMapper.class);
System.out.println("第一次执行:"+mapper1.selectBlogById(1));
System.out.println("第二次执行:"+mapper2.selectBlogById(1));
} finally {
sqlSession1.close();
sqlSession2.close();
}
}
测试结果:
二级缓存需要跟事务绑定才能生效
代码测试:
@Test
public void update() throws IOException {
String resource = "mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);
BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
Blog blog = mapper1.selectBlogById(1);
System.out.println("第一次执行:"+blog);
sqlSession1.commit();
System.out.println("第二次执行:"+mapper2.selectBlogById(1));
} finally {
sqlSession1.close();
sqlSession2.close();
}
}
测试结果:
测试不同session修改数据库,查询数据是否是二级缓存数据
测试代码:
@Test
public void update2() throws IOException {
String resource = "mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
try {
BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);
BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
BlogMapper mapper3 = sqlSession3.getMapper(BlogMapper.class);
Blog blog = mapper1.selectBlogById(1);
System.out.println("第一次执行:"+blog);
sqlSession1.commit();//提交事务
blog.setName("xxxx");
System.out.println("第二次执行修改:"+mapper2.updateByPrimaryKey(blog));
sqlSession2.commit();
System.out.println("第三次执行:"+mapper3.selectBlogById(1));
} finally {
sqlSession1.close();
sqlSession2.close();
sqlSession3.close();
}
}
测试结果: