zoukankan      html  css  js  c++  java
  • Mybatis源码分析之Cache一级缓存原理(四)

    之前的文章我已经基本讲解到了SqlSessionFactory、SqlSession、Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存,

    它的缓存分为一级缓存和二级缓存,本文我们主要分析下一级缓存。


    先看一个例子,代码还是之前(第一篇)的的demo

        public static void main(String[] args) throws Exception {
            SqlSessionFactory sessionFactory = null;
            String resource = "configuration.xml";
            sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
            SqlSession sqlSession = sessionFactory.openSession();
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            System.out.println(userMapper.findUserById(1));
            System.out.println(userMapper.findUserById(1));
        }

    上面代码我们执行了两次userMapper.findUserById(1),结果如下图


    从执行结果看,DB的查询只有1次,那么第二次的查询结果是在怎么来的呢?



    一:什么是一级缓存


       每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。 在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。      

    为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

         基本的流程示意图如下:

         

         

         对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。

         

         

    二:如何执行缓存


        我们知道mybatis的SQL执行最后是交给了Executor执行器来完成的,我们看下BaseExecutor类的源码

        

     @Override
         public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
           BoundSql boundSql = ms.getBoundSql(parameter);
           CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
           return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
        
        
        
        
         @SuppressWarnings("unchecked")
         @Override
         public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
           ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
           if (closed) {
             throw new ExecutorException("Executor was closed.");
           }
           if (queryStack == 0 && ms.isFlushCacheRequired()) {
             clearLocalCache();
           }
           List<E> list;
           try {
             queryStack++;
             list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//localCache 本地缓存
             if (list != null) {
               handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
             } else {
               list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);  //如果缓存没有就走DB
             }
           } finally {
             queryStack--;
           }
           if (queryStack == 0) {
             for (DeferredLoad deferredLoad : deferredLoads) {
               deferredLoad.load();
             }
             // issue #601
             deferredLoads.clear();
             if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
               // issue #482
               clearLocalCache();
             }
           }
           return list;
         }
         
         
         
      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);//清空现有缓存数据
        }
        localCache.putObject(key, list);//新的结果集存入缓存
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }


    通过上面的三个方法我们基本已经看明白了缓存的使用,它的本地缓存使用的是PerpetualCache类,内部其实还是一个HashMap,只是稍微做了封装而已。

    我们再看下天的Key是如何生成的

     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);



      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId());
        cacheKey.update(Integer.valueOf(rowBounds.getOffset()));
        cacheKey.update(Integer.valueOf(rowBounds.getLimit()));
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (int i = 0; i < parameterMappings.size(); i++) {
          ParameterMapping parameterMapping = parameterMappings.get(i);
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              value = parameterObject;
            } else {
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
          }
        }
        if (configuration.getEnvironment() != null) {
          // issue #176
          cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
      }

    它是通过statementId,params,rowBounds,BoundSql来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果。


    我们用一张图来看清楚一级缓存的基本流程(本图网上早来的,自己懒得画了)


    三:一级缓存生命周期


    1. MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,

    Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

    2. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

    3. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

    4.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。







  • 相关阅读:
    Kafka~Linux环境下的部署
    Zookeeper~Linux环境下的部署
    pwd显示链接文件的真实路径
    3种不同方式的焦点图轮播
    软件集成策略——如何有效率地提升质量
    寻找直方图中面积最大的矩形 --- 庞果网
    再谈线程
    SQL 常用基础语句
    初识Maven
    公司存在的问题
  • 原文地址:https://www.cnblogs.com/jeffen/p/6284070.html
Copyright © 2011-2022 走看看