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调用查询的整个流程了 。


  • 相关阅读:
    博客开篇--别让自己在临终前后悔
    预言帖,WP7迟早有一天会失败
    sql server 列值转列名的问题
    “《面对面做好每一天》中国短道速滑教练李琰”读后感
    原型要做到什么程度
    不得不承认我的高度不够,通过项目提升了.
    项目进度很慢
    原型确认后准备开发(1)
    邮件发送打印机更改打印机连接的通知
    onclick事件中加href
  • 原文地址:https://www.cnblogs.com/wbo112/p/15087597.html
Copyright © 2011-2022 走看看