Mybatis的一级缓存和二级缓存详解

缓存原理图:

Mybatis的一级缓存和二级缓存详解

一、一级缓存(本地缓存)

sqlSession级别的缓存。(相当于一个方法内的缓存)

每一次会话都对应自己的一级缓存,作用范围比较小,一旦会话关闭就查询不到了;

一级缓存默认是一直开启的,是SqlSession级别的一个Map;
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;

测试:

Mybatis的一级缓存和二级缓存详解

取缓存中的数据:

Mybatis的一级缓存和二级缓存详解

说明:当再次查询发现已经有数据了,就直接在缓存中返回之前查的数据,而不再访问数据库;

二、一级缓存失效的四种情况:

没有使用到当前一级缓存的情况,效果就是:还需要再向数据库发出查询

1、sqlsession不同(会话不同)

Mybatis的一级缓存和二级缓存详解

结果:

Mybatis的一级缓存和二级缓存详解

说明:不同的会话的缓存不共享数据

2、sqlsession相同,查询缓存中没有的数据

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,查询条件不同
        Employee emp03 = mapper.getEmpById(3);
        System.out.println(emp03);
        System.out.println(emp01==emp03);
 
    }finally{
        openSession.close();
    }
}

结果:

Mybatis的一级缓存和二级缓存详解

说明:当缓存中没有数据时,会重新查数据库

3、sqlsession相同,但两次查询之间执行了增删改操作

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
        mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
        System.out.println("数据添加成功");
        
        Employee emp02 = mapper.getEmpById(1);
        System.out.println(emp02);
        System.out.println(emp01==emp02);
        
    }finally{
        openSession.close();
    }
}

结果:

Mybatis的一级缓存和二级缓存详解

说明:为了防止增删改对当前数据的影响,即使查的同一个对象,也会重新查数据库

原因:每个增删改标签都有默认清空缓存配置:flushCache="true",不过这是默认的是一级和二级缓存都清空

Mybatis的一级缓存和二级缓存详解

4、sqlsession相同,但手动清楚了一级缓存(缓存清空)

清空缓存:openSession.clearCache();

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,手动清除了一级缓存(缓存清空)
        openSession.clearCache();
        
        Employee emp02 = mapper.getEmpById(1);
        System.out.println(emp02);
        System.out.println(emp01==emp02);
        
    }finally{
        openSession.close();
    }
}

结果:

Mybatis的一级缓存和二级缓存详解

说明:手动清空缓存后,需要重新查数据库

三、二级缓存(全局缓存)

基于namespace名称空间级别的缓存:一个namespace对应一个二级缓存

即一个mapper.xml对应一个缓存:

Mybatis的一级缓存和二级缓存详解

1、工作机制:

*         1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
*         2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
*         3、sqlSession===EmployeeMapper==>Employee
*                         DepartmentMapper===>Department
*             不同namespace查出的数据会放在自己对应的缓存中(map)
*             效果:数据会从二级缓存中获取
*                 查出的数据都会被默认先放在一级缓存中。
*                 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

2、 使用:
*             1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
*             2)、去mapper.xml中配置使用二级缓存:
*                 <cache></cache>
*             3)、我们的POJO需要实现序列化接口

1)在mybatis全局配置文件中开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>

Mybatis的一级缓存和二级缓存详解

2)在mapper.xml中配置使用二级缓存

直接加上: <cache><cache/>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
    
     <cache><cache/>
 
     <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     <select >
         select * from tbl_employee where last_name like #{lastName}
     </select>
</mapper>

或者 <cache>中配置一些参数:

eviction:缓存的回收策略:
    • LRU – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    • 默认的是 LRU。
flushInterval:缓存刷新间隔
    缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
    true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
             mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
    false:非只读:mybatis觉得获取的数据可能会被修改。
            mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type="":指定自定义缓存的全类名;
        实现Cache接口即可;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
 
     <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> 
 
     <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     <select >
         select * from tbl_employee where last_name like #{lastName}
     </select>
</mapper>

3)POJO需要实现序列化接口

Mybatis的一级缓存和二级缓存详解

测试:

注意:需要openSession.close();后,才能从二级缓存中查数据;

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
 
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
        
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        openSession.close();
        
        //第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession2.close();
        
    }finally{
        
    }
}

结果:

Mybatis的一级缓存和二级缓存详解

Mybatis的一级缓存和二级缓存详解

说明:第二次查询是从二级缓存中拿到的数据,并没有发送新的sql;

注意:

如果openSession.close();在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据:

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
 
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
        
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession.close();
        openSession2.close();
        
    }finally{
        
    }
}

结果:

Mybatis的一级缓存和二级缓存详解

说明:第二次又重新发送了sql,因为从二级缓存中取数据时,会话没关闭所以二级缓存中没数据,所以又去一级缓存中查询,也没有数据则发送了sql查数据库;

所以,只有会话关闭或提交后,一级缓存中的数据才会转移到二级缓存中,然后因为是同一个namespace所以可以获取到数据;

关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行顺序

四、和缓存有关的设置/属性

1)、mybatis全局配置文件中配置全局缓存开启和清空

1.1)控制二级缓存的开启和关闭

         <setting name="cacheEnabled" value="true"/>

         cacheEnabled=true:开启缓存;false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)

1.2)控制一级缓存的开启和关闭

         <setting name="localCacheScope" value="SESSION"/>

         localCacheScope:本地缓存作用域(一级缓存SESSION);

         当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存;

注意:一级缓存关闭后,二级缓存自然也无法使用;  

2)、方法中sqlSession清除缓存测试

sqlSession.clearCache();只是清除当前session的一级缓存;

如果openSession清空了缓存,即执行了openSession.clearCache()方法:

Mybatis的一级缓存和二级缓存详解

结果:

Mybatis的一级缓存和二级缓存详解

 说明:openSession清空缓存不影响二级缓存;只清空了一级缓存;因为在openSession.close()时,就将一级缓存保存至了二级缓存; 

3)、mapper.xml中也可以配置一级和二级缓存开启和使用

3.1)每个select标签都默认配置了useCache="true":
         如果useCache= false:则表示不使用缓存(一级缓存依然使用,二级缓存不使用)
3.2)每个增删改标签默认配置了flushCache="true":(一级二级都会清除)

Mybatis的一级缓存和二级缓存详解
增删改执行完成后就会清除缓存;

测试:默认flushCache="true":一级缓存和二级缓存都会被清空;

执行增加操作:

Mybatis的一级缓存和二级缓存详解

结果:

Mybatis的一级缓存和二级缓存详解

注意:查询标签<select>默认flushCache="false":如果flushCache=true;每次查询之后都会清空缓存;一级和二级缓存都无法使用;

五、整合第三方缓存

mybatis通过map实现的缓存,很不专业;此时可以通过整合第三方缓存来达到缓存的目的;

做法:实现Cache.java接口中的方法即可:

如实现putObject()方法,往缓存中写数据;实现getObject()方法,从缓存中获取数据;至于什么时候调用,由mybatis决定;

Mybatis的一级缓存和二级缓存详解