zoukankan      html  css  js  c++  java
  • Springboot中mybatis执行逻辑源码分析

    Springboot中mybatis执行逻辑源码分析

    在上一篇springboot整合mybatis源码分析已经讲了我们的Mapper接口,userMapper是通过MapperProxy实现的一个动态代理,所有调用userMapper的方法,最终都会代理到MapperProxy的invoke方法上,我们这次就来看看mybatis具体的执行流程。为了简单易懂,本次的示例用的是最简单的查询语句且不包含事务。

    本篇文档的源码路径https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis


    1. 我们在业务代码中调用userMapper.findAll()会调用到MapperProxy的invoke方法,我就从这里开始吧

        //MapperProxy类的方法
      	
      	//proxy就是我们userMapper生成的代理类,method当前是findAll(),args当前是个map "{id=1, table=user}"
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          try {
             //如果是Object的方法,就直接通过反射执行方法
            if (Object.class.equals(method.getDeclaringClass())) {
              return method.invoke(this, args);
            } else {
              //当前的方法findAll不是Object的,所以会走到这里
              return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      

         //MapperProxy类的方法
        private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
          try {
            //这里会将方法进行缓存。当method不存在于methodCache中时,创建一个MapperMethodInvoker,添加到methodCache中的
             //methodCache你在MapperProxyFactory.newInstance方法的时候,从MapperProxyFactory类中传递过来的
             //而MapperProxyFactory这个类是在添加mapper类的时候,MapperRegistry.addMapper方法中构造出来的, knownMappers.put(type, new MapperProxyFactory<>(type));所以我们这里的methodCache是每个mapper类都会有一个
            return MapUtil.computeIfAbsent(methodCache, method, m -> {
              //如果方法的修饰符是public,没有abstract,static修饰的话就会走这里,由于我们的mapper是接口,我们的方法也是abstract的,所以不会走到这个分支
              if (m.isDefault()) {
                try {
                  if (privateLookupInMethod == null) {
                    return new DefaultMethodInvoker(getMethodHandleJava8(method));
                  } else {
                    return new DefaultMethodInvoker(getMethodHandleJava9(method));
                  }
                } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                    | NoSuchMethodException e) {
                  throw new RuntimeException(e);
                }
              } else {
                //所以我们这里最终会创建一个PlainMethodInvoker,添加到methodCache中,我们先看看MapperMethod的构造方法
                return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
              }
            });
          } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
          }
        }
      

        
        //MapperMethod类的方法
        public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
         //command主要是用mapperInterface和method在config中查找对应sql的定义,返回sql id和  sqlCommandType 
          this.command = new SqlCommand(config, mapperInterface, method);
         //method主要是通过解析我们的method,得到方法的返回类型,参数类型,分页等等相关信息  
          this.method = new MethodSignature(config, mapperInterface, method);
        }
      

      构造完 new PlainMethodInvoker之后就会调到它的invoke方法去处理,继续调用到上面MapperMethod的execute方法,我们进去看看

        //所有的增删改查都是在这里走不同的分支来处理的,我们当前的是查询,我们看看select分支
         public Object execute(SqlSession sqlSession, Object[] args) {
          Object result;
          switch (command.getType()) {
            case INSERT: {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.insert(command.getName(), param));
              break;
            }
            case UPDATE: {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.update(command.getName(), param));
              break;
            }
            case DELETE: {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.delete(command.getName(), param));
              break;
            }
            case SELECT:
              if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
              //我们这里的findAll()方法返回值是个List,所以会走到method.returnsMany()这个分支,我们进去看看 
              } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
              } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
              } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
              } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                    && (result == null || !method.getReturnType().equals(result.getClass()))) {
                  result = Optional.ofNullable(result);
                }
              }
              break;
            case FLUSH:
              result = sqlSession.flushStatements();
              break;
            default:
              throw new BindingException("Unknown execution method for: " + command.getName());
          }
          if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
          }
          return result;
        }
      

      	//MapperMethod的方法 
       	private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
          List<E> result;
          //这里会对参数做个处理,如果mapper方法有多个参数的话,会封装到一个map里面,我们这里只有一个参数,已经是map了,所以我们这里只是从Object[]返回args[0] 
          Object param = method.convertArgsToSqlCommandParam(args);
          //通过mapper方法上有没有 RowBounds.class参数来判断是否有分页,我们这里没有
          if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectList(command.getName(), param, rowBounds);
          } else {
             //所以我们这里会走到这个分支中 
            result = sqlSession.selectList(command.getName(), param);
          }
          // issue #510 Collections & arrays support
          if (!method.getReturnType().isAssignableFrom(result.getClass())) {
            if (method.getReturnType().isArray()) {
              return convertToArray(result);
            } else {
              return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            }
          }
          return result;
        }
      

      会调用到SqlSessionTemplate的selectList方法,继续调用到sqlSessionProxy的selectList方法,这个sqlSessionProxy也是个动态代理,是在SqlSessionTemplate构造方法中初始化的

        public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
            PersistenceExceptionTranslator exceptionTranslator) {
      
          notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
          notNull(executorType, "Property 'executorType' is required");
      
          this.sqlSessionFactory = sqlSessionFactory;
          this.executorType = executorType;
          this.exceptionTranslator = exceptionTranslator;
          this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
              new Class[] { SqlSession.class }, new SqlSessionInterceptor());
        }
      

      调用sqlSessionProxy的方法,最终都会调用到SqlSessionInterceptor(这是个内部类,在SqlSessionTemplate类中)的invoke方法

          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           //这里会返回一个真正执行sql的SqlSession,我们进 getSqlSession方法去看看
            SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      	......
        }
      
      

       //SqlSessionUtils的方法
       
        public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
            PersistenceExceptionTranslator exceptionTranslator) {
      
          notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
          notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
      	//如果开启了事务,在同一个事务中,第一次返回的holder是空的,后面返回的holder都不为空,直接holder中返回SqlSession
          SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
      	//如果holder不为空,根据executorType相同,返回之前缓存的SqlSession
          SqlSession session = sessionHolder(executorType, holder);
          if (session != null) {
            return session;
          }
      
          LOGGER.debug(() -> "Creating a new SqlSession");
          //开启了事务,在同一个事务中,第一次执行,或者没有开启事务,就会走到这里  
          session = sessionFactory.openSession(executorType);
      	//如果开启了事务,就会将创建出来的session进行缓存
          registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
      
          return session;
        }
      

      我们看看session = sessionFactory.openSession(executorType)这句,最终会调用到DefaultSqlSessionFactory.openSessionFromDataSource中

        private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
          Transaction tx = null;
          try {
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            //这里会创建一个SpringManagedTransaction  
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //在这里根据execType创建Executor,当前的execType是一个SIMPLE,会创建SimpleExecutor,  如果开启了缓存,就会再创建CachingExecutor,包装SimpleExecutor
            final Executor executor = configuration.newExecutor(tx, execType);
              //最终返回DefaultSqlSession对象
            return new DefaultSqlSession(configuration, executor, autoCommit);
          } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
          } finally {
            ErrorContext.instance().reset();
          }
        }
      

      我们再回头看SqlSessionInterceptor.invoke后面的执行,创建完了SqlSession,就会去调用Object result = method.invoke(sqlSession, args),我们去看看这里的执行,这句是通过反射来执行的,这里的sqlSession是上面创建的DefaultSqlSession类来完成的

      我们来看看DefaultSqlSession的方法,所有的增删改查方法都有具体的实现,我们最终mapper的方法都是通过这个类来实现的。

      我们看看本次调用的selectList方法

        //最终会调用到这个
        private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
          try {
            //根据sql id获取到  MappedStatement(这个包含了我们sql语句的定义的详细信息)
            MappedStatement ms = configuration.getMappedStatement(statement);
            //这里会继续去调用CachingExecutor的query方法
            return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
          } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
          } finally {
            ErrorContext.instance().reset();
          }
        }
      

      //CachingExecutor的方法
      
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //这里主要是会对我们的sql语句中"${"、"}"和"#{"、"}" 及之间的内容进行替换。
        //将"${"、"}"根据里面的内容直接进行字符串替换
        //将"#{"、"}"替换成?,根据参数顺序将参数封装到parameterMappings中
        //我们的sql语句是" SELECT * FROM ${table} where id =#{id}",我们的入参map中有table=user,这里就会进行替换,替换之后的sql就是 "SELECT * FROM user where id =?"
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        //这里会生成一个查询缓存的key  
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        //继续走到这里去看看  
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
      

        
        //CachingExecutor的方法
        @Override
        public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException {
          //会在这里获取缓存,这里的缓存也就是我们常说的二级缓存了,由于我们的mapper.xml文件中配置了<cache/>,所以这里的cache也就不会为空,这个cache在<mapper>标签下面,所以每个mapper的cache都是各自来控制的。
           //这里的缓存最终是LruCache,看名字就知道这是一种LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,
           //Cache有多种实现,具体要那种,我们是可以指定的 
           //在其他的update,delete等等方法会调用 flushCacheIfRequired(ms);将缓存清空
          Cache cache = ms.getCache();
          if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
              ensureNoOutParams(ms, boundSql);
              @SuppressWarnings("unchecked")
              //在这里首先会在缓存中查询,如果缓存有,就从缓存中获取,直接返回  
              List<E> list = (List<E>) tcm.getObject(cache, key);
              if (list == null) {
                //继续会调用到这里,这里的delegate是SimpleExecutor ,这个方法在它的父类,BaseExecutor中
                list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                 //在这里会将查村出来的结果缓存到TransactionalCache.entriesToAddOnCommit中,
                 //注意:这里并没有缓存到cache里面
                tcm.putObject(cache, key, list); // issue #578 and #116
              }
              return list;
            }
          }
           
          return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        }
      

        //BaseExecutor的方法
        @SuppressWarnings("unchecked")
        @Override
        public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
      		......
          try {
            queryStack++;
             //在这里会从 localCache中查找,这个其实就我们说的一级缓存真正存放的位置。这个localCache是当前类的一个属性,而在没有开启事务的时候,我们每次都会新创建一个SimpleExecutor,所以这个localCache也就都是空的
             //如果开启事务,在同一个事务中,第一次请求会创建 SimpleExecutor,之后都是重用同一个SimpleExecutor
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
              handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
              //我们进这个里面去看看  
              list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
          } finally {
            queryStack--;
          }
          if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
              deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
            //这里的LocalCacheScope默认是LocalCacheScope.SESSION,如果是LocalCacheScope.STATEMENT的话就会清空缓存
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
              // issue #482
              clearLocalCache();
            }
          }
          return list;
        }
      

      	//BaseExecutor的方法
        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 {
              //在这里会获取connection,执行数据库的查询,并返回我们需要的类型
            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;
        }
      

      我们现在退回到SqlSessionTemplate.invoke方法看获取到结果后的处理

        //SqlSessionTemplate的方法
        private class SqlSessionInterceptor implements InvocationHandler {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
                SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
            try {
              //获取到执行结果,在这里返回  
              Object result = method.invoke(sqlSession, args);
               //由于我们本次不涉及事务,所以会从这个分支返回 
              if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                // force commit even on non-dirty sessions because some databases require
                // a commit/rollback before calling close()
                 //在这里会将我们之前缓存到TransactionalCache.entriesToAddOnCommit中的返回结果,存储到MappedStatement.cache中
                 //由于在添加到cache中会调用,serialize((Serializable) object),通过序列化返回byte[]数组,所以如果我们xml文件中开启了缓存,那我们返回结果包含的类就需要实现Serializable接口 
                sqlSession.commit(true);
              }
              return result;
            } catch (Throwable t) {
             ......
            } finally {
              if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
              }
            }
          }
        }
      

      上面就是整个springboot中mybatis调用查询的整个流程了 。


  • 相关阅读:
    HDU5418.Victor and World(状压DP)
    POJ2686 Traveling by Stagecoach(状压DP)
    POJ3254Corn Fields(状压DP)
    HDU5407.CRB and Candies(数论)
    CodeForces 352D. Jeff and Furik
    CodeForces 352C. Jeff and Rounding(贪心)
    LightOj 1282 Leading and Trailing
    Ural 1057. Amount of Degrees(数位DP)
    HDU 2089 不要62 (数位DP)
    HDU5366 The mook jong (DP)
  • 原文地址:https://www.cnblogs.com/wbo112/p/15087597.html
Copyright © 2011-2022 走看看