MyBatis的一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。
我们在Mapper里面设置方法如下所示:
/** * 根据员工id查询员工信息 * * @return */ Emp getEmpById(@Param("empId") Integer empId);
我们设置映射文件如下所示:
<select id="getEmpById" resultType="Emp"> select * from t_emp where emp_id=#{empId} </select>
我们设置测试类如下所示:
@Test public void testGetEmpById(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); CacheMapper mapper = sqlSession.getMapper(CacheMapper.class); Emp emp1= mapper.getEmpById(1); System.out.println(emp1); Emp emp2= mapper.getEmpById(1); System.out.println(emp2); }
我们运行之后如下所示:
我们发现查询了两次,但是sql只运行了一次,第一次是从咱们的数据库里面查的,第二次是从缓存里面查的。
我们将我们的测试类修改如下所示:
/** * Mybatis的一级缓存: * MyBatis的一级缓存是SqlSession级别的,即通过同一个SqlSession查询的数据会被缓存 * 再次使用同一个SqlSession查询同一条数据,会从缓存中获取 */ @Test public void testGetEmpById(){ SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp emp1= mapper1.getEmpById(1); System.out.println(emp1); Emp emp2= mapper1.getEmpById(1); System.out.println(emp2); SqlSession sqlSession2 = SqlSessionUtil.getSqlSession(); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); Emp emp3 = mapper2.getEmpById(1); System.out.println(emp3); }
我们运行之后如下所示:
我们发现第一次查询是在sql里面查的,第二次是在缓存里面查的,后面又执行了一次sql,这次我们重新创建了一个sqlSession,所以上面里面已经没有他的缓存信息,我们需要重新执行sql进行查找。
我们通过同一个sqlsession所查询出来的数据,会被缓存,当我们再次通过同一个sqlsession去查询相同的数据的时候,会直接从缓存里面进行查找,并不会从我们的数据库里面重新去查了。
使一级缓存失效的四种情况:
(1)不同的SqlSession对应不同的一级缓存
(2)同一个SqlSession但是查询条件不同
(3)同一个SqlSession两次查询期间执行了任何一次增删改操作
我们设计方法进行验证一下:
/** * 添加员工信息 * @param emp */ void insertEmp(Emp emp);
我们设计的映射文件如下所示:
<insert id="insertEmp" > insert into t_emp values(null,#{empName},#{age},#{gender},null) </insert>
我们设计的测试类如下所示:
@Test public void testGetEmpById(){ SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp emp1= mapper1.getEmpById(1); System.out.println(emp1); mapper1.insertEmp(new Emp(null,"小虎",25,"男")); Emp emp2= mapper1.getEmpById(1); System.out.println(emp2); }
我们运行之后如下所示:
我们发现再次查找1的时候sql语句再次执行,再次从数据库里面进行查找 。
(4)同一个SqlSession两次查询期间手动清空了缓存
我们设置的测试类如下所示:
@Test public void testGetEmpById(){ SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp emp1= mapper1.getEmpById(1); System.out.println(emp1); //清空sqlsession级别的缓存 sqlSession1.clearCache(); Emp emp2= mapper1.getEmpById(1); System.out.println(emp2); }
我们运行之后如下所示:
MyBatis的二级缓存:
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
<a>在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
<b>在映射文件中设置标签<cache/>
<mapper namespace="com.rgf.mybatis.mapper.CacheMapper"> <cache/>
<c>二级缓存必须在SqlSession关闭或提交之后有效,否则会先保存在一级缓存里面
我们设置的测试类如下所示:
@Test public void testCache() throws IOException { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession1 = sqlSessionFactory.openSession(true); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp emp1 = mapper1.getEmpById(1); System.out.println(emp1); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); Emp emp2 = mapper2.getEmpById(1); System.out.println(emp2); }
我们运行之后如下所示:
我们发现查询同一条数据,sql语句仍然运行了两次。
我们将SqlSession关闭之后重新运行,关闭之后,才会将存放在一级缓存里面的东西存放到二级缓存里面,如下所示:
@Test public void testCache() throws IOException { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession1 = sqlSessionFactory.openSession(true); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp emp1 = mapper1.getEmpById(1); System.out.println(emp1); sqlSession1.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); Emp emp2 = mapper2.getEmpById(1); System.out.println(emp2); sqlSession2.close(); }
运行之后如下所示:
错误提示我们Emp没有序列化的异常
我们进行下一条件。
<d>查询的数据所转换的实体类类型必须实现序列化的接口
我们在Emp实体类里面找到如下所示:
实现序列化接口。public class Emp implements Serializable { private Integer empId; private String empName; private Integer age; private String gender;
我们重新运行之后如下所示:
我们发现此时的sql语句即执行了一个 。
Mybatis的二级缓存是SqlsessionFactory级别的,即通过同一个SqlSessionFactory所获取的 SqlSession对象,然后查询的数据会被缓存,然后再次通过同一个SqlSessionFactory所获取的SqlSession对象查询相同的数据,然后会从缓存里面进行获取。
我们发现该日志里面存在一个一级缓存里面没有的:
Cache Hit Ratio
这是缓存命中率,这个与我们当前查询的总数量有关系。
使二级缓存失效的一种情况:
同一个SqlSessionFactory两次查询期间执行了任何一次增删改操作
二级缓存的相关配置:
在mapper配置文件中添加的cache标签可以设置一些属性:(这些都有默认值,一般不需要配置)
(1)evication属性:缓存回收策略,默认的是LRU。
LRU:最近最少使用的:移除最长时间不被使用的对象
FIFO:先进先出:按对象进入缓存的顺序来移除他们
SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
(2)fiushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
使二级缓存刷新可以通过两次查找过程中实现增删改。
(3)size属性:引用数目,正整数(默认)
代表缓存最多可以存储多少个对象,太大容易导致内存溢出。
(4)readOnly属性:只读,true/false
true:只读缓存:会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改,这提供了很重要的性能优势
false:读写缓存;会返回缓存对象的拷贝(通过序列化),修改了拷贝后,对象并没有被修改。这会慢一些,但是安全,默认是false.
MyBatis缓存查询的顺序:
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
整合第三方缓存EHCache(针对二级缓存)
1、添加依赖:
<!--Mybatis EHCache整合包-->
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!--slf4j日志门面的一个具体实现-->
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
我们导入依赖之后如下所示:
2、各jar包功能
jar包名称 | 作用 |
mybatis-ehcache | Mybatis和EHCcache的整合包 |
ehcache | EHCache核心包 |
slf4-api | SLF4J日志门面包 |
logback-classic | 支持SLF4J门面接口的一个具体实现 |
3. 创建EHCache的配置文件ehcache.xml
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../resources/ehcache.xsd">
<!--磁盘保存路径-->
<diskStore path="D:\rgf\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true">
</defaultCache>
</ehcache>
4设置二级缓存的类型。