缓存
缓存的作用就是方便快速的查询,将从数据库中查询出来的经常使用并且不经常改变的数据放在内存中,这样更有助于用户的快速查询,这样也能减少数据库和服务器的压力。
Mybatis中提供了两种缓存机制,Mybatis默认是开启缓存的,而且它的默认缓存机制是以及缓存
- 一级缓存
- 二级缓存
当然,Mybatis也支持自定义缓存机制
一级缓存
一级缓存是Mybatis默认的缓存机制,该缓存存在于一个SqlSession的生命周期中。
感觉画的这个图有点更复杂了。。。。
博主来解释一下吧:
就是说,当我们获得一个SqlSession之后,我们利用该SqlSession去进行查询,查询出来的内容会被Mybatis自动的放到这个一级缓存中,直到该SqlSession被关闭,在这段过程中,就是一级缓存的生命周期。在这段时间内进行的查询,Mybatis会先从缓存中查找,如果缓存中没有再查询数据库,然后将从数据库中查询出来的数据放入缓存,如果缓存中有,就直接查询出缓存中的数据,从而减少对数据库的操作次数,从一定程度上提升了程序的性能。
在缓存的正常生命周期中,有以下几种方式会让缓存失效:
- 在这期间SqlSession进行了增删改操作
- 手动销毁缓存
sqlSession.clearCache();
我们直接举一个例子吧
比如我们需要从数据库中查询一个指定ID的用户:
@Test
public void getUserByIdTest() throws InterruptedException {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//查询id为1的用户
User user1 = userMapper.getUserById(1);
System.out.println(user1);
Thread.sleep(1000);
System.out.println("============");
//再次查询id为1的用户
User user2 = userMapper.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
上面的代码,按照我们的理解,它肯定会去操作两次数据库,其实并不是,下面展示运行时的日志输出:
这也说明了我们执行第二次查询的时候是直接从内存中操作,而且查询出来的和第一次查询结果一致。
这里再提供一个对比的测试代码:
@Test
public void getUserByIdTest() throws InterruptedException {
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.getUserById(1);
System.out.println(user1);
//=====================================
Thread.sleep(1000);
System.out.println("============");
//=====================================
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession1.close();
sqlSession2.close();
}
这个代码就模拟两个不同的SqlSession进行查询,结果如下:
我们明显的发现,它就执行了两次SQL语句。
二级缓存
二级缓存只针对于一个Mapper文件,它的启动方式就是在SQL映射文件中添加一行:
<cache/>
以上简单语句的效果如下:
- 开启二级缓存之后,该映射文件中的所有select语句的结果都会被缓存
- 该映射文件中所有的增删改语句都会刷新缓存
- 默认的二级缓存会使用最近最少使用算法来清除不需要的缓存
- 该缓存会不定时的进行刷新
- 该缓存会保存列表或对象的1024个引用
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
这个<cache>
标签含有如下属性:
- eviction:指定清除策略
- LRU:最近最少使用,移除最长时间不被使用的对象(默认)
- FIFO:先进先出,按对象进入缓存的顺序来移除它们
- SOFT:软引用,基于垃圾回收器状态和软引用规则来移除对象
- WEAK:弱引用,更积极的基于垃圾收集器状态和弱引用规则移除对象
- flushInterval:指定刷新间隔,单位毫秒
- size:指定缓存中可以放置的最大对象数
- readOnly:是否只读。
- true:只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这就提供了可观的性能提升
- false:可读写的缓存会通过序列化返回缓存对象的拷贝,速度相对来说慢一点,但是更加安全,默认问false
注意:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
我们也来测试一波:
先在Mapper.xml中开启默认二级缓存,及添加<cache/>
即可
然后测试代码修改如下:
@Test
public void getUserByIdTest() throws InterruptedException {
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.getUserById(1);
System.out.println(user1);
sqlSession1.close();
//=====================================
Thread.sleep(1000);
System.out.println("============");
//=====================================
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession2.close();
}
测试结果:
这里需要注意的是,需要前一个SqlSession完成提交、回滚或者关闭之后,二级缓存才会更新。
还有个需要注意的地方,如果实体类没有被序列化,会出现报错,序列化实体类如下:
package com.ara.pojo;
import java.io.Serializable;
public class User implements Serializable {
private int id;
private String name;
private String password;
}
这样开启二级缓存才能够正常使用。
总结:
- 只要开启了二级缓存,就在一个Mapper中有效。
- 查询出来的结果会先放在一级缓存中,当SqlSession提交、回滚或者关闭后才会存到二级缓存中。
- 如要使用二级缓存,需要将实体类序列化,否则会报错
一级缓存和二级缓存的关系:
实际查询调用中:
- 它会先查看二级缓存中是否存在对应数据
- 有,就直接返回
- 没有就查看一级缓存中是否存在对应数据
- 一级缓存中有就直接返回
- 一级缓存中没有就查询数据库
- 查出来的数据先存到一级缓存
- 然后sqlSession提交、回滚或者关闭,就将一级缓存中的数据存入二级缓存。