mybatis的缓存
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
一级缓存基于sqlSession,二级缓存基于namespace
一级缓存
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。一旦发生增删改,缓存立即失效
测试
IStudentDao studentDao = sqlSession.getMapper(IStudentDao.class); // 两次查询操作,只有第一次调用sql Student student = studentDao.findStudentById(45); student = studentDao.findStudentById(45); Student student1 = new Student(); student1.setName("xiaoming"); student1.setId(46); student1.setSex("F"); // studentDao.updateStudent(student1); // System.out.println("----------------"); //清空缓存 sqlSession.clearCache(); //当同一个session 发生了 增删改时 那么换粗立即失效 === sqlSession.clearCache(); student = studentDao.findStudentById(45);
二级缓存
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
缓存流程图
二级缓存的开启
1.在mybatis开启
<settings> <!--开启二级缓存--> <setting name="cacheEnabled" value="true"/> </settings>
2.在mapper 中开启二级缓存
<mapper namespace="com.wgz.dao.IStudentDao"> <!--开启二级缓存--> <cache > <property name="eviction" value="LRU" /> <property name="flushInterval" value="60000" /> <property name="size" value="1024" /> <property name="readOnly" value="false" /> </cache> </mapper>
-
eviction
: 缓存回收策略,有这几种回收策略
-
LRU - 最近最少回收,移除最长时间不被使用的对象
-
FIFO - 先进先出,按照缓存进入的顺序来移除它们
-
SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
-
WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
-
默认是 LRU 最近最少回收策略
-
flushinterval
缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值 -
readOnly
: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改 -
size
: 缓存存放多少个元素 -
type
: 指定自定义缓存的全类名(实现Cache 接口即可) -
blocking
: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
3.使用缓存
-
useCache="true" 默认为true
<!--使用resultMap--> <select id="findStudentById" parameterType="java.lang.Integer" resultType="student" useCache="true"> select * from student_tb where id=#{id} </select>
4测试
// 3.获取session SqlSession sqlSession1 = sqlSessionFactory.openSession(); // 4.使用sqlSession 创建代理对象 IStudentDao studentDao1 = sqlSession1.getMapper(IStudentDao.class); Student student1 = studentDao1.findStudentById(45); System.out.println("student1:"+student1); //清空缓存 sqlSession1.close(); // 获取新的SqlSession 但是sql 语句没有执行,这是应为 可以去二级缓存去取,多个session 共享缓存 SqlSession sqlSession2 = sqlSessionFactory.openSession(); IStudentDao studentDao2 = sqlSession2.getMapper(IStudentDao.class); Student student2=studentDao2.findStudentById(45); System.out.println("student2:"+student2);
注意
-
一级二级缓存调用顺序:先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此;
-
使用二级缓存的实体类必须实现序列化;方便存储
-
sqlsession只有关闭或者提交事务时才会把提交二级缓存,否则二级缓存不生效
-
sqlsession只要发生增删改就会把所有缓存清除,二级缓存只有当前命名空间发生增删改,命名空间的缓存才会清除
mybatis 一级缓存和二级缓存产生脏数据的问题?
使用二级缓存,当一个session发生修改时,另一个session如果缓存过以前的一级查询结果可能会有脏数据
// 1.读取配置文件 inputStream = Resources.getResourceAsStream("sqlMapConfig.xml"); // 2.配置连接功工厂 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); // 3.获取sqlSession 就是一个会话 连接 mybatis 默认是开启事务的 sqlSession1 = sqlSessionFactory.openSession(); IStudentDao iStudentDao1 = sqlSession1.getMapper(IStudentDao.class); System.out.println("使用sqlSession1 查询"); Student student1 = iStudentDao1.findStudentById(63); System.out.println("student1:"+student1); //sqlSession1.close(); sqlSession2 = sqlSessionFactory.openSession(); IStudentDao iStudentDao2 = sqlSession2.getMapper(IStudentDao.class); System.out.println("使用sqlSession2 查询"); Student student2 = iStudentDao2.findStudentById(63); System.out.println("student2:"+student2); student2 = iStudentDao2.findStudentById(63); System.out.println("student2:"+student2); // 在 session2中修改数据 此时二级缓存失效 student2.setHeight(1600); iStudentDao2.updateStudent(student2); sqlSession2.commit(); // 因为二级缓存实现,iStudentDao1 先去二级缓存取,失效改用取一级缓存,此时能拿到数据但是不是 session2中修改数据的最新数据产生 脏数据 student1 = iStudentDao1.findStudentById(63); System.out.println("student1:"+student1);
解决方法
第一种.关闭一级缓存,并未查询的session改为自动提交
在spring中也不一定为发生,一般为单例的mapper每次创建新的sqlsession(若开启事务则时同一sqlsession),所以可以避免,不用担心
<!-- 将一级缓存修改为STATEMENT 不使用一级缓存 SESSION 使用一级缓存 --> <setting name="localCacheScope" value="STATEMENT"/>
public static void main(String[] args) { InputStream inputStream = null; SqlSession sqlSession1 = null; SqlSession sqlSession2 = null; try { // 1.读取配置文件 inputStream = Resources.getResourceAsStream("sqlMapConfig.xml"); // 2.配置连接功工厂 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); // 3.获取sqlSession 就是一个会话 连接 mybatis 默认是开启事务的 sqlSession1 = sqlSessionFactory.openSession(true); IStudentDao iStudentDao1 = sqlSession1.getMapper(IStudentDao.class); System.out.println("使用sqlSession1 查询"); Student student1 = iStudentDao1.findStudentById(63); System.out.println("student1:"+student1); //sqlSession1.close(); sqlSession2 = sqlSessionFactory.openSession(); IStudentDao iStudentDao2 = sqlSession2.getMapper(IStudentDao.class); System.out.println("使用sqlSession2 查询"); Student student2 = iStudentDao2.findStudentById(63); System.out.println("student2:"+student2); student2 = iStudentDao2.findStudentById(63); System.out.println("student2:"+student2); // 在 session2中修改数据 此时二级缓存失效 student2.setHeight(333); iStudentDao2.updateStudent(student2); sqlSession2.commit(); Thread.sleep(4000); // 因为二级缓存实现,iStudentDao1 先去二级缓存取,失效改用取一级缓存,此时能拿到数据但是不是 session2中修改数据的最新数据产生 脏数据 Student student3 = iStudentDao1.findStudentById(63); System.out.println("student3:"+student3); } catch (Exception e) { e.printStackTrace(); // 有异常回滚 sqlSession1.rollback(); }finally { // 5.释放资源 sqlSession1.close(); try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
第二种:不使用缓存
<select id="findStudentById" parameterType="java.lang.Integer" resultType="com.baidu.entity.Student" useCache="false" > <include refid="selectAll"></include> where id = #{id} </select>