zoukankan      html  css  js  c++  java
  • mybatis源码学习(三)-一级缓存二级缓存

    本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点

     1.一级缓存配置信息

     2.一级缓存源码学习笔记

     3.二级缓存配置信息

     4.二级缓存源码

     5.一级缓存、二级缓存总结

    1.一级缓存配置:

      一级缓存是SqlSession级别的,同一个sqlSession执行多次相同的查询语句时,第二次会从缓存中获取数据,不查询数据库;一级缓存是无法关闭的,默认是SESSION级别

      mybatis一级缓存默认是开启的,一级缓存有两个配置值,SESSION级别和STATEMENT级别,这两个级别的区别是:

       如果是SESSION级别(默认值),那么在同一个sqlSession中,都会共享这个缓存

       如果是STATEMENT级别,那么会把一级缓存中的缓存清空,也就是说,同一个sqlSession,多次请求同一个sql,每次都会查询数据库

    默认是session级别,此时为开启二级缓存:

    如果把一级缓存的级别设置为STATEMENT,那么同一个sqlSession执行多次相同的SQL,都会查询数据库:

     

    那下面,我们来源码中验证

    2.一级缓存源码:

      1.首先会根据statement等参数,生成缓存中需要用到的key  org.apache.ibatis.executor.CachingExecutor#createCacheKey

      2.根据key从一级缓存中获取值,一级缓存是PerpetualCache这个类  

       org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

      3.如果从一级缓存中取到值,就不查询数据库;否则查询数据库

        org.apache.ibatis.executor.BaseExecutor#handleLocallyCachedOutputParameters

      4.如果一级缓存中没有取到值,就从数据库中查询,并将数据库中查询到的数据,存到一级缓存中

        org.apache.ibatis.executor.BaseExecutor#queryFromDatabase

      5.判断缓存级别是否是STATEMENT,如果是,将一级缓存清空

     1 @SuppressWarnings("unchecked")
     2 @Override
     3 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     4 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
     5 if (closed) {
     6   throw new ExecutorException("Executor was closed.");
     7 }
     8 if (queryStack == 0 && ms.isFlushCacheRequired()) {
     9   clearLocalCache();
    10 }
    11 List<E> list;
    12 try {
    13   queryStack++;
    14   list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    15   if (list != null) {
    16     handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    17   } else {
    18     list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    19   }
    20 } finally {
    21   queryStack--;
    22 }
    23 if (queryStack == 0) {
    24   for (DeferredLoad deferredLoad : deferredLoads) {
    25     deferredLoad.load();
    26   }
    27   // issue #601
    28   deferredLoads.clear();
    29   if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    30     // issue #482
    31     clearLocalCache();
    32   }
    33 }
    34 return list;
    35 }

       这段源码中,第14行,是尝试从一级缓存中根据key,取值;如果不为null,就从一级缓存中取值,第18行,是从数据中查询数据;

       在 queryFromDatabase()方法中,会将数据库中查询到的数据,put到一级缓存中

       源码中的第29行,就是对一级缓存是否是STATEMENT级别做判断,如果是,就clear;

      我们再来说,insert、update、delete,这三种操作,都会走update,

      org.apache.ibatis.executor.BaseExecutor#update

      

    1 @Override
    2 public int update(MappedStatement ms, Object parameter) throws SQLException {
    3   ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    4   if (closed) {
    5     throw new ExecutorException("Executor was closed.");
    6   }
    7   clearLocalCache();
    8   return doUpdate(ms, parameter);
    9 }

    可以看到,执行doUpdate()方法之前,会先clear一级缓存

    3.二级缓存配置

      二级缓存是mapper级别的,需要配置才会开启

      需要在mybatis的配置文件中,增加以下配置

      

    1 <settings>
    2     <setting name="cacheEnabled" value="true"/>
    3 </settings>

    并且在mapper.xml文件中增加

    <cache></cache>

    这样,二级缓存就开启了

    4.二级缓存源码

      二级缓存的源码,首先我们要看 org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)  这个方法

     1 @Override
     2 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
     3   throws SQLException {
     4  Cache cache = ms.getCache();
     5 if (cache != null) {
     6   flushCacheIfRequired(ms);
     7   if (ms.isUseCache() && resultHandler == null) {
     8     ensureNoOutParams(ms, parameterObject, boundSql);
     9     @SuppressWarnings("unchecked")
    10     List<E> list = (List<E>) tcm.getObject(cache, key);
    11     if (list == null) {
    12       list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    13       tcm.putObject(cache, key, list); // issue #578 and #116
    14     }
    15     return list;
    16   }
    17 }
    18 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    19 }

    在第7行,首先会判断,当前sql,是否开启useCache这个属性,对于select 默认是true;update、insert、delete默认是 false;这个点的源码是在解析xml的时候,最终会把每个select、update、insert、delete节点

    build成一个mappedStatement,在build之前,有这么一段代码

    1 boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    2 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    3 boolean useCache = context.getBooleanAttribute("useCache", isSelect);

    org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

    这里的意思是:如果是select,那么useCache就是true,否则就是false

    接着上面的说,第10行会尝试从二级缓存中获取数据,这里的调用链是这样的

    从源码中,可以看到,二级缓存先于以及缓存,如果二级缓存中没有取到值,会充以及缓存中国取,一级缓存中没有取到,再从数据库查询数据

    这个调用链这几个cache,还没有搞明白,具体的作用,后面学习之后,会更新;

    二级缓存有一个点,需要明确:二级缓存只有在sqlSession执行commit的时候,才会更新;否则是不生效的

     这个是commit的时候,二级缓存Put的调用链

    5.总结

      一级缓存是sqlSession级别的,二级缓存是mapper级别的,二级缓存的粒度更细,

      一级缓存无法关闭,二级缓存可以开启或者关闭

      一级缓存如果在分布式的场景下,可能会存在脏数据;

     如果使用缓存,感觉还是Redis靠谱吧;

      

      

  • 相关阅读:
    10 Unit Testing and Automation Tools and Libraries Java Programmers Should Learn
    nginx unit java 试用
    Oracle Trace文件生成及查看
    记录数过亿条的表数据维护-数据删除
    对于上千万甚至上亿的数据,如何正确的删除?
    怎么快速删除大数据量表
    如何启动或关闭oracle的归档(ARCHIVELOG)模式
    oracle清理归档日志(缓存)
    HTTP和HTTPS协议,看一篇就够了
    HTTP与HTTPS对访问速度(性能)的影响
  • 原文地址:https://www.cnblogs.com/mpyn/p/11982371.html
Copyright © 2011-2022 走看看