MyBatis源码阅读--缓存

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011676300/article/details/82937233

MyBatis源码阅读-总索引

MyBatis源码阅读–缓存

前言

MyBatis提供了缓存机制减轻数据库压力,提高数据库性能。
MyBatis的缓存分为两级:一级缓存、二级缓存。
一级缓存是SqlSession级别的缓存,缓存的数据只在SqlSession内有效。
二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的。

一级缓存

一级缓存是默认打开的,不能够进行更改。

实例

log.info("执行第一个SqlSession.........");
        SysUser sysUser1  = null;
        SqlSession sqlSession1  = SqlSessionUtil.getSqlSession();
        try {
            sysUserMapper = sqlSession1.getMapper(SysUserMapper.class);

            log.info("执行第一次查询.........");
            sysUser1 = sysUserMapper.selectByPrimaryKey(1030L);
            log.info("查询结果  sysUser-1 = " + sysUser1);

            log.info("执行第二次查询.........");
            SysUser sysUser2 = sysUserMapper.selectByPrimaryKey(1030L);
            log.info("查询结果  sysUser-2 = " + sysUser2);

            log.info("sysUser1和sysUser2是否是同一个对象?---" + (sysUser1 == sysUser2));
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            sqlSession1.close();
        }

        log.info("执行第二个SqlSession.........");
        SqlSession sqlSession2  = SqlSessionUtil.getSqlSession();
        try {
            sysUserMapper = sqlSession2.getMapper(SysUserMapper.class);

            log.info("执行第三次查询.........");
            SysUser sysUser3 = sysUserMapper.selectByPrimaryKey(1030L);
            log.info("查询结果  sysUser-3 = " + sysUser3);

            log.info("执行第四次查询.........");
            SysUser sysUser4 = sysUserMapper.selectByPrimaryKey(1030L);
            log.info("查询结果 sysUser-4 = " + sysUser4);

            log.info("sysUser3和sysUser4是否是同一个对象?---" + (sysUser3 == sysUser4));

            log.info("sysUser1和sysUser3是否是同一个对象?---" + (sysUser1 == sysUser3));

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            sqlSession2.close();
        }

输出

  INFO [main] - 执行第一个SqlSession.........
 INFO [main] - 执行第一次查询.........
DEBUG [main] - ==>  Preparing: select id, user_name, user_password, user_email, 
create_time, user_info, head_img from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1030(Long)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, create_time, user_info, head_img
TRACE [main] - <==        Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0, <<BLOB>>, <<BLOB>>
DEBUG [main] - <==      Total: 1
 INFO [main] - 查询结果  sysUser-1 = SysUser(id=1030, userName=liqia, userPassword=789, 
 userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 
 INFO [main] - 执行第二次查询.........
 INFO [main] - 查询结果  sysUser-2 = SysUser(id=1030, userName=liqia, userPassword=789,
 userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 INFO [main] - sysUser1和sysUser2是否是同一个对象?---true
 
 INFO [main] - 执行第二个SqlSession.........
 
 INFO [main] - 执行第三次查询.........
DEBUG [main] - ==>  Preparing: select id, user_name, user_password, user_email, create_time,
 user_info, head_img from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1030(Long)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, create_time, user_info, head_img
TRACE [main] - <==        Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0, <<BLOB>>, <<BLOB>>
DEBUG [main] - <==      Total: 1
 INFO [main] - 查询结果  sysUser-3 = SysUser(id=1030, userName=liqia, userPassword=789, 
 userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 
 INFO [main] - 执行第四次查询.........
 INFO [main] - 查询结果 sysUser-4 = SysUser(id=1030, userName=liqia, userPassword=789, 
 userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 INFO [main] - sysUser3和sysUser4是否是同一个对象?---true
 INFO [main] - sysUser1和sysUser3是否是同一个对象?---false

从上面可以看出,由于一级缓存的存在,当查询条件一样的时候,MyBatis第二次查询并没有去执行SQL语句,而是直接使用第一次查询时缓存中数据。
并且两次查询的对象是同一个,说明第一次查询之后,其将查询结果进行了缓存。
sysUser1和sysUser3是两个不同的对象,因为是不同的SqlSession,不同的SqlSession之间的缓存是无效的。
当对该数据表进行任何的INSERT,UPDATE,DELETE操作时,都会清空缓存。

二级缓存

二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的。

配置二级缓存

第一步:MyBatis配置文件配置cacheEnabled
查看类Configuration可以看出,MyBatis二级缓存默认是打开的。

public Configuration() {
   .....................................
        this.cacheEnabled = true;
   ......................................
 }

也可以在配置文件中配置启用或禁用缓存。

 <settings>
        <!-- 这个配置使全局的映射器启用或禁用缓存 -->
        <setting name="cacheEnabled" value="true" />
 </settings>       

第二步:Mapper.xml文件配置 节点

<?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.mybatis.dao.mapper.SysUserMapper" >
<!--  -->
  <cache/>
</mapper>  

二级缓存配置完成。

<cache/>

这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句将会被缓存。
  • 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
  • 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
  • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不* 干扰其他调用者或线程所做的潜在修改。

指定参数

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。

1.eviction(可用的回收策略)

  • LRU – 最近最少使用的:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    2.flushInterval:刷新间隔,默认不设置,即没有刷新间隔,缓存仅仅在 调用语句时刷新。
    3.size:引用数目。
    4.readOnly:只读的缓存会给调用者返回缓存对象的相同实例,因此这些对象不能被修改,这个提供了很重要的性能优势。可读写的缓存会通过序列化返回对象的拷贝,这种方式会慢一些,但是安全。默认是false.

Cache是在MapperBuilderAssistant类中进行创建的。
org.apache.ibatis.builder.MapperBuilderAssistant

 Cache cache = (new CacheBuilder(this.currentNamespace))
                         .implementation((Class)this.valueOrDefault(typeClass, PerpetualCache.class))
                         . addDecorator((Class)this.valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval)
                         . size(size).readWrite(readWrite).blocking(blocking).properties(props).build();
       

cache节点是在org.apache.ibatis.builder.xml.XMLMapperBuilder类中进行解析的,如果对应的参数为null,则会设置相应的默认值。

private void cacheElement(XNode context) throws Exception {
       if (context != null) {
           String type = context.getStringAttribute("type", "PERPETUAL");
           Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);
           //LRU
           String eviction = context.getStringAttribute("eviction", "LRU");
           Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);
           Long flushInterval = context.getLongAttribute("flushInterval");
           Integer size = context.getIntAttribute("size");
           boolean readWrite = !context.getBooleanAttribute("readOnly", false);
           boolean blocking = context.getBooleanAttribute("blocking", false);
           Properties props = context.getChildrenAsProperties();
           this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
       }
   }

测试

二级缓存配置完成以后,运行上面的代码。

INFO [main] - 执行第一个SqlSession........
 INFO [main] - 执行第一次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select id, user_name, user_password, user_email, create_time, user_info, head_img from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1030(Long)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, create_time, user_info, head_img
TRACE [main] - <==        Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0, <<BLOB>>, <<BLOB>>
DEBUG [main] - <==      Total: 1
 INFO [main] - 查询结果  sysUser-1 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 INFO [main] - 执行第二次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.0
 INFO [main] - 查询结果  sysUser-2 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 INFO [main] - sysUser1和sysUser2是否是同一个对象?---true
 INFO [main] - 关闭sqlSession1

 INFO [main] - 执行第二个SqlSession.........
 INFO [main] - sqlSession1 == sqlSession2 ? false
 INFO [main] - 执行第三次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.3333333333333333
 INFO [main] - 查询结果  sysUser-3 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 INFO [main] - 执行第四次查询.........
DEBUG [main] - Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.5
 INFO [main] - 查询结果 sysUser-4 = SysUser(id=1030, userName=liqia, userPassword=789, userEmail=Mali@qq.com, createTime=Wed Mar 14 14:53:11 CST 2018, userInfo=updatetest, headImg=[1, 2, 3])
 INFO [main] - sysUser3和sysUser4是否是同一个对象?---true
 INFO [main] - sysUser1和sysUser3是否是同一个对象?---true

可以看到输出结果中多了这条语句,也就是缓存命中率。

Cache Hit Ratio [com.mybatis.dao.mapper.SysUserMapper]: 0.3333333333333333

由于只有在执行sqlSession1.close();后,才会更新二级缓存。
所以第一次和第二次查询的命中率都是0;
第三次为0.3333,因为查询3次,命中一次。
第四次为0.5,因为查询4次,命中两次。

当设置为可读写缓存

<cache    readOnly="false"/>

输出为

 INFO [main] - sysUser3和sysUser4是否是同一个对象?---false
 INFO [main] - sysUser1和sysUser3是否是同一个对象?---false

当设置为只读缓存

<cache    readOnly="true"/>

输出为

 INFO [main] - sysUser3和sysUser4是否是同一个对象?---true
 INFO [main] - sysUser1和sysUser3是否是同一个对象?---true

这是因为设置为只读缓存时,使用map来保存对象,多次读取到的都是同一个对象。
当设置为可读写缓存,使用序列化来实现缓存,多次获取对象都是通过反序列化得到,因此是不同的缓存对象,但是对象的值是一样的。

猜你喜欢

转载自blog.csdn.net/u011676300/article/details/82937233