Mybatis二级缓存原理

Mybatis二级缓存原理记录是一种精神,是加深理解最好的方式之一。最近看了下Mybatis的源码,分析了二级缓存的实现方式,在这里把他记下来。虽然这不复杂,对这方面的博客也有很多,写的也很好。但我坚信看懂了是其一,能够教别人或者描述清楚记下来才能真正的掌握。曹金桂cao_jingui@163.com(如有欠缺还请指教)时间:2016年10月11日16:00这篇文章能够帮你学会对Mybatis配置…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

记录是一种精神,是加深理解最好的方式之一。

最近看了下Mybatis的源码,分析了二级缓存的实现方式,在这里把他记下来。虽然这不复杂,对这方面的博客也有很多,写的也很好。但我坚信看懂了是其一,能够教别人或者描述清楚记下来才能真正的掌握。
曹金桂 cao_jingui@163.com (如有欠缺还请指教)
时间:2016年10月11日16:00

这篇文章能够帮你

  • 学会对Mybatis配置二级缓存
  • 学会Mybatis二级缓存的实现方式
  • 学会整合外部缓存框架(如:Ehcache)
  • 学会自定义二级缓存

1. Mybatis内部二级缓存的配置

要使用Mybatis的二级缓存,需要对Mybatis进行配置,配置分三步

  • Mybatis全局配置中启用二级缓存配置
<setting name="cacheEnabled" value="true"/>
  • 在对应的Mapper.xml中配置cache节点
<mapper namespace="userMapper">
    <cache />
    <result ... />
    <select ... />
</mapper>
  • 在对应的select查询节点中添加useCache=true
<select id="findUserById" parameterType="int" resultMap="user" useCache="true">
    select * from users where id=#{id};
</select>
  • 高级配置

a. 为每一个Mapper分配一个Cache缓存对象(使用<cache>节点配置)
b. 多个Mapper共用一个Cache缓存对象(使用<cache-ref>节点配置)

只要简单的三步配置即可开启Mybatis的二级缓存了。在使用mybatis查询时候(“userMapper.findUserById”),不同会话(Sqlsession)在查询时候,只会查询数据库一次,第二次会从二级缓存中读取。

@Before
public void before() {
    String mybatisConfigFile = "MybatisConfig/Mybatis-conf.xml";
    InputStream stream = TestMybatis.class.getClassLoader().getResourceAsStream(mybatisConfigFile);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream); //构建sqlSession的工厂
}
@Test
public void test() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    User i = sqlSession.selectOne("userMapper.findUserById", 1);
    System.out.println(i);
    sqlSession.close();
    sqlSession = sqlSessionFactory.openSession();
    User x = sqlSession.selectOne("userMapper.findUserById", 1); // 读取二级缓存数据
    System.out.println(x);
    sqlSession.close();
}

2. Mybatis内部二级缓存的设计及工作模式

Mybatis二级缓存原理

 

首先我们要知道,mybatis的二级缓存是通过CacheExecutor实现的。CacheExecutor其实是Executor的代理对象。所有的查询操作,在CacheExecutor中都会先匹配缓存中是否存在,不存在则查询数据库。

3. 内部二级缓存的实现详解

竟然知道Mybatis二级缓存是通过CacheExecotur实现的,那看下Mybatis中创建Executor的过程

// 创建执行器(Configuration.newExecutor)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
   //确保ExecutorType不为空(defaultExecutorType有可能为空)
   executorType = executorType == null ? defaultExecutorType : executorType;
   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
   Executor executor;
   if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
   } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
   } else {
      executor = new SimpleExecutor(this, transaction);
   }
   if (cacheEnabled) { //重点在这里,如果启用全局代理对象,返回Executor的Cache包装类对象
      executor = new CachingExecutor(executor);
   }
   executor = (Executor) interceptorChain.pluginAll(executor);
   return executor;
}

重点在cacheEnabled这个参数。如果你看了我的文章[Mybatis配置文件解析过程详解],就应该知道了怎么设置cacheEnabled。对,就是此文章第一点说的开启Mybatis的全局配置项。我们继续看下CachingExecutor具体怎么实现的。

public class CachingExecutor implements Executor {
    private Executor delegate;
    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        flushCacheIfRequired(ms); //是否需要更缓存
        return delegate.update(ms, parameterObject);  //更新数据
    }
    ......
}

很清晰,静态代理模式。在CachingExecutor的所有操作都是通过调用内部的delegate对象执行的。缓存只应用于查询,我们看下CachingExecutor的query方法。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //创建缓存值
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //获取记录
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            // ensureNoOutParams
            if (ms.getStatementType() == StatementType.CALLABLE) {
                for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                    if (parameterMapping.getMode() != ParameterMode.IN) {
                        throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
                    }
                }
            }
            List<E> list = (List<E>) tcm.getObject(cache, key); //从缓存中获取数据
            if (list == null) {
                list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                tcm.putObject(cache, key, list); // 结果保存到缓存中
            }
            return list;
        }
    }
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

如果MappedStatement中对应的Cache存在,并且对于的查询开启了二级缓存(useCache=”true”),那么在CachingExecutor中会先从缓存中根据CacheKey获取数据,如果缓存中不存在则从数据库获取。这里的代码很简单,很容易理解。
说到缓存,有效期和缓存策略不得不提。在Mybatis中二级缓存也实现了有效期的控制和缓存策略。Mybatis中是使用装饰模式实现的,具体可以看下mybatis的cache包

 

Mybatis二级缓存原理

 

具体于配置如下:

<cache eviction="FIFO|LRU|SOFT|WEAK" flushInterval="300" size="100" />

对应具体实现源码可以参考CacheBuilder类的源码。

public Cache build() {
    if (implementation == null) { //缓存实现类
        implementation = PerpetualCache.class;
        if (decorators.size() == 0) {
            decorators.add(LruCache.class);
        }
    }
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    if (PerpetualCache.class.equals(cache.getClass())) {
        for (Class<? extends Cache> decorator : decorators) {
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache);
        }
        // 采用默认缓存包装类
        cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
        cache = new LoggingCache(cache);
    }
    return cache;
}

4. 一级缓存和二级缓存的使用顺序

如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:

二级缓存 ———> 一级缓存——> 数据库

5. mybatis二级缓存和分页插件同时使用产生的问题

问题:分页插件开启二级缓存后,分页查询时无论查询哪一页都返回第一页的数据

在之前讲解Mybatis的执行流程的时候提到,在开启cache的前提下,Mybatis的executor会先从缓存里读取数据,读取不到才去数据库查询。问题就出在这里,sql自动生成插件和分页插件执行的时机是在statementhandler里,而statementhandler是在executor之后执行的,无论sql自动生成插件和分页插件都是通过改写sql来实现的,executor在生成读取cache的key(key由sql以及对应的参数值构成)时使用都是原始的sql,这样当然就出问题了。
找到问题的原因后,解决起来就方便了。只要通过拦截器改写executor里生成key的方法,在生成可以时使用自动生成的sql(对应sql自动生成插件)或加入分页信息(对应分页插件)就可以了。
参考:http://blog.csdn.net/hupanfeng/article/details/16950161

6. mybatis整合第三方缓存框架

Mybatis二级缓存原理

 

我们以ehcache为例。对于ehcache我只会简单的使用。这里我只是介绍Mybatis怎么使用ehcache,不对ehcache配置作说明。我们知道,在配置二级缓存时候,我们可以指定对应的实现类。这里需要mybatis-ehcache-1.0.3.jar这个jar包。在Mapper中我们只要配置如下即可。

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/> 

当然,项目中ehcache的配置还是需要的。

小结

对于Mybatis整合第三方的缓存,实现骑士很简单,只要在配置的地方制定实现类即可。
Mybatis默认二级缓存的实现在集群或者分布式部署下是有问题的,Mybatis默认缓存只在当节点内有效,并且对缓存的失效操作无法同步的其他节点。需要整合第三方分布式缓存实现,如ehcache或者自定义实现。

作者:曹金桂
链接:https://www.jianshu.com/p/5ff874fa696f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/191361.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • linux smartctl 命令,Smartctl 命令查看硬盘详细信息

    linux smartctl 命令,Smartctl 命令查看硬盘详细信息Smartctl命令查看硬盘详细信息(2011-08-3014:21:41)标签:linux硬盘信息使用时间杂谈1.1什么是Smartmontools?Smartmontools是一种硬盘检测工具,通过控制和管理硬盘的SMART(SelfMonitoringAnalysisandReportingTechnology,自动检测分析及报告技术)技术来实现的,SMART技术可以对硬盘的磁头单…

    2022年6月17日
    54
  • 网关,路由器,局域网,广域网络_局域网和广域网是什么意思

    网关,路由器,局域网,广域网络_局域网和广域网是什么意思网关、路由器、局域网、广域网网关:连接一个网络到另一个网络的关口,不同的网络有不同的网络地址,以及不同的网络中的主机分配有不同的段的ip地址。网关可以实现广域网的互连,也可以实现局域网的互连现在通常说的网关就是路由器的IP只要两台电脑能够相互ping的通,就说明两台电脑属于在同一个局域网中。路由器局域网就是内网,而内网就是局域网,一个学校,一个单位就是一个局域网,在同一个局域网中,所有的ip都是不一样的,但是在两个不同的局域网中,ip地址可以一样,使用ipconfig

    2022年10月9日
    2
  • 用状态空间法求猴子香蕉问题_猴子摘香蕉状态空间图

    用状态空间法求猴子香蕉问题_猴子摘香蕉状态空间图猴子和香蕉问题(monkeyandbananaproblem)在一个房间内有一只猴子(可把这只猴子看做一个机器人)、一个箱子和一束香蕉。香蕉挂在天花板下方,但猴子的高度不足以碰到它。那么这只猴子怎样才能摘到香蕉呢?图2.1.1表示出猴子、香蕉和箱子在房间内的相对位置。用一个四元表列(W,x,Y,z)来表示这个问题的状态,其中W-猴子的水平位置x-当猴子在箱子顶上时取x=1;否则取x=0Y-箱…

    2022年9月26日
    8
  • 皮尔森相关系数(Pearson correlation coefficient)「建议收藏」

    皮尔森相关系数(Pearson correlation coefficient)「建议收藏」概述定义物理意义皮尔森距离机器学习中的应用代码实现概述皮尔森相关系数也称皮尔森积矩相关系数(Pearsonproduct-momentcorrelationcoefficient),是一种线性相关系数,是最常用的一种相关系数。记为r,用来反映两个变量X和Y的线性相关程度,r值介于-1到1之间,绝对值越大表明相关性越强。定义总体相关系数ρ定义为两…

    2022年4月20日
    599
  • pycharm的调试功能_pycharm运行调试配置

    pycharm的调试功能_pycharm运行调试配置Debug调试,是对于学习编程人员来说是一项重要的技能。只有当你学会debug了以后,才可以正确的知道程序的走向流程是如何的,DEBUG是排除程序故障的意思。debug则是通过工具来对代码进行调试,进而一步步找出程序中出现bug的位置,也就是程序中具体错误代码的位置。Pycharm中的debug模式首先,还是用示例说话,我们先写一段简短的代码,来帮助我们完成今天要讲的内容。初学者可能没见过for循环中的下划线‘_’,在Python中是占位符的意思,因…

    2022年8月25日
    12
  • 一阶惯性环节的性能分析——自动控制原理基础补充(二)

    一阶惯性环节的性能分析——自动控制原理基础补充(二)以前读书的时候学习自动控制原理,就是为了考试,各种相频幅频特性题咣咣做,一点都不含糊,但是实际代表什么意义一点都不知道。现在真是发现,这个东西有用得一批。这篇文章从一阶惯性环节为切入点,对自动控制原理进行一个简单的复习。还蛮喜欢博客里面写东西的,按照自己思路,按照逻辑一点一点往下,不像发文章八股文一样。1一阶惯性环节的bode图对于这个非常常见的一阶惯性系统而言,其关键指标就是截止频率。截止频率的定义:从频域响应的角度讲,当保持输入信号的幅度不变,改变频率使输出信号降至最大值的0.707倍

    2022年10月5日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号