全网最全MyBatis基于XML使用系列四:缓存

1,076 阅读6分钟

Mybatis系列

1、全网最详细的Mybatis介绍和基本使用

2、全网最全MyBatis基于XML使用系列一:全局配置详解

3、全网最全MyBatis基于XML使用系列二:参数、返回结果处理

4、网最全MyBatis基于XML使用系列三:动态Sql

5、全网最全MyBatis基于XML使用系列四:缓存

为什么要使用缓存

当用户多次查询相同的数据的时候,如果不需要缓存,那么每次都需要访问数据库,从而增加数据库的压力,当使用缓存之后,第一次将这些数据从数据库中查询,将查询的数据保存到缓存中,当用户再次查询相同的数据时,不用直接访问数据库进行查询,直接取缓存里面查询。这样可以减少网络连接和数据库查询带来的损耗,提高代码的查询效率,减少高并发访问带来的系统性能问题。

总的来说:查询一些不经常变化的数据,使用缓存可以提高查询效率,减少并发问题的发生。

Mybatis缓存主要分为一级缓存和二级缓存

一级缓存

Mybatis的一级缓存主要是SqlSession级别,默认是主动开启的。

基本概念

一般来说参数和sql完全一样的情况下,第一次执行SQL语句,使用SqlSession查询数据库,Mybatis会把结果集存放到缓存中并返回结果,如果第二次一直到第n次执行SQL语句进行查询,那么SqlSession就会查询当前第一次缓存的数据直接返回,而不会再次请求数据库查询。

image.png

基本验证

@Test
public void test01(){
     SqlSession sqlSession=sqlSessionFactory.openSession();
     try{
         EmpMapper mapper=sqlSession.getMapper(EmpMapper.class);
         List<Emp> list=mapper.selectAllEmp();
         for(Emp emp:list){
             System.out.println(emp);
        }
         System.out.println("=============");
         List<Emp> list2=mapper.selectAllEmp();
         for(Emp emp:list2){
             System.out.println(emp);
        }
    }catch(Exception e){
         e.printStackTrace();
    }finally{
         sqlSession.close();
    }

分析:虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为1的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。

一级缓存失效情况

开启多个SqlSession,缓存失效

一级缓存是sqlSession级别的缓存,如果在应用程序中只有开启了多个sqlsession,那么会造成缓存失效

@Test
public void test02() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    List<Emp> list = mapper.selectAllEmp();
    for (Emp emp : list) {
        System.out.println(emp);
    }
    System.out.println("================================");
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
    List<Emp> list2 = mapper2.selectAllEmp();
    for (Emp emp : list2) {
        System.out.println(emp);
    }
    sqlSession.close();
    sqlSession2.close();
}

参数不一致,缓存失效

发送过程中发生了数据的修改,缓存失效

@Test
public void test03() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    System.out.println("================================");
    empByEmpno.setEname("zhangsan");
    int i = mapper.updateEmp(empByEmpno);
    System.out.println(i);
    System.out.println("================================");
    Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno1);
    sqlSession.close();
}

在两次查询期间,手动去清空缓存,缓存失效

@Test
public void test03() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    System.out.println("================================");
    System.out.println("手动清空缓存");
    sqlSession.clearCache();
    System.out.println("================================");
    Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno1);
    sqlSession.close();
}

总结

特点:

  • 1.默认就开启了,也可以关闭一级缓存 localCacheScope=STATEMENT
  • 2.作用域:是基于sqlSession(默认),一次数据库操作会话。
  • 3.缓存默认实现类PerpetualCache ,使用map进行存储的
  • 4.查询完就会进行存储
  • 5.先从二级缓存中获取,再从一级缓存中获取 * key==> sqlid+sql

失效情况:

  • 1.不同的sqlSession会使一级缓存失效
  • 2.同一个SqlSession,但是查询语句不一样
  • 3.同一个SqlSession,查询语句一样,期间执行增删改操作
  • 4.同一个SqlSession,查询语句一样,执行手动清除缓存

二级缓存

二级缓存是namespace级别的缓存,他比一级缓存更加底层,一般情况下Mybatis是默认不开启二级缓存的。

如果需要开启二级缓存那么则需要实现一下两个条件

  • 实体类必须序列化
  • 在xml配置文件中配置cache标签

image.png

基本实现

  • 1、全局配置文件中添加如下配置:
<settings>
<!--因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存。-->
    <setting name="cacheEnabled" value="true"/>
</settings>
  • 2、配置EmpMapper.xml映射
<!--当前映射文件开启二级缓存-->
<cache></cache>
  • 3、修改实体类必须要实现Serializable接口

  • 4、测试实现

@Test
public void test04() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    sqlSession.close(); 
    Emp empByEmpno1 = mapper2.findEmpByEmpno(1111);
    System.out.println(empByEmpno1);
    sqlSession2.close();
}

缓存标签属性

  • eviction:表示缓存回收策略,默认是LRU

    • LRU:最近最少使用的,移除最长时间不被使用的对象

    • FIFO:先进先出,按照对象进入缓存的顺序来移除

    • SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象

    • WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象

  • flushInternal:刷新间隔,单位毫秒

    默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

  • size:引用数目,正整数

    代表缓存最多可以存储多少个对象,太大容易导致内存溢出

  • readonly:只读,true/false

    • true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。

    • false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值

@Test
public void test05() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    Emp empByEmpno = mapper.findEmpByEmpno(1111);
    System.out.println(empByEmpno);
    sqlSession.close(); 
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
    Emp empByEmpno2 = mapper2.findEmpByEmpno(1111);
    System.out.println(empByEmpno2);
    Emp empByEmpno3 = mapper2.findEmpByEmpno(1111);
    System.out.println(empByEmpno3); 
    Emp empByEmpno4 = mapper2.findEmpByEmpno(7369);
    System.out.println(empByEmpno4);
    Emp empByEmpno5 = mapper2.findEmpByEmpno(7369);
    System.out.println(empByEmpno5);
    sqlSession2.close();
}

总结

特性:

  • 1.默认开启了,没有实现
  • 2.作用域:基于全局范围,应用级别。
  • 3.缓存默认实现类PerpetualCache ,使用map进行存储的但是二级缓存根据不同的mapper命名空间多包了一层map
  • 4.事务提交的时候(sqlSession关闭)
  • 5.先从二级缓存中获取,再从一级缓存中获取 *

实现:

  • 1.开启二级缓存<setting name="cacheEnabled" value="true"/>

  • 2.在需要使用到二级缓存的映射文件中加入,基于Mapper映射文件来实现缓存的,基于Mapper映射文件的命名空间来存储的

  • 3.在需要使用到二级缓存的javaBean中实现序列化接口implements Serializable

    • 配置成功就会出现缓存命中率 同一个sqlId: 从缓存中拿出的次数/查询总次数

失效:

  • 1.同一个命名空间进行了增删改的操作,会导致二级缓存失效

    但是如果不想失效:可以将SQL的flushCache 这是为false,但是要慎重设置,因为会造成数据脏读问题,除非你能保证查询的数据永远不会执行增删改

  • 2.让查询不缓存数据到二级缓存中useCache="false"

  • 3.如果希望其他mapper映射文件的命名空间执行了增删改清空另外的命名空间就可以设置:

    <cache-ref namespace="com.mybatis.mapper.DeptMapper"/>