美文网首页
Mybatis 执行Sql流程

Mybatis 执行Sql流程

作者: 瞿大官人 | 来源:发表于2019-05-06 15:06 被阅读0次

    前言

    对于mybatis之前已经讲了mybatis 中接口注入spring源码分析mybatis 接口依赖注入源码分析。现在mybatis的接口能够放入Spring,并且能够依赖注入,这一节讲的重点就是以下代码的具体流程。

        @Autowired
        private CardMapper cardMapper;
        public void select() {
            cardMapper.selectCardById(1);
        }
    

    重点关注类

    MapperProxy:
    SqlSessionTemplate:
    SqlSessionUtils:
    SqlSessionFactory:
    Configuration:
    CachingExecutor:
    Executor:
    SqlSession:
    org.apache.ibatis.transaction.Transaction:
    Connection:
    RoutingStatementHandler:
    StatementHandler:
    Statement:
    ResultSetHandler

    一创建SqlSession

    SqlSession用于对外执行sql,并且一般来说SqlSession使用了之后就要关闭掉,防止内存泄漏。既然说的是执行sql流程,那么SqlSession的创建就一定是避不开的。通常我们都会结合Spring一起使用mybatis,并且SqlSession的创建,关闭、提交、回滚都交给Spring管理,对于使用者来说完全无感知。就如下面这段代码,执行Sql的时候,用户完全不需要关心SqlSession的管理。这里SqlSession的创建我们具体指SqlSessionTemplateDefaultSqlSession

     @Autowired
        private CardMapper cardMapper;
        public void select() {
            cardMapper.selectCardById(1);
        }
    

    1.1 创建SqlSessionTemplate

    SqlSessionTemplateSpring用来管理SqlSession的生命周期的,并且这个类是全局唯一的,线程安全的,在项目启动的时候就会创建,创建后最后会赋值到MapperProxy中。先简单的看下SqlSessionTemplate构造函数

    //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());
      }
      @Override
      public <T> T selectOne(String statement) {
    //    使用代理查询
        return this.sqlSessionProxy.<T> selectOne(statement);
      }
    

    SqlSessionInterceptorSqlSessionTemplate内部类,并且实现了InvocationHandler接口,用于代理SqlSession,所以只要是sqlSessionProxy调用了SqlSession的方法都会调用SqlSessionInterceptor中的invoke方法。具体的invoke方法我会在下面讲解。这里先贴下创建SqlSessionTemplate的代码,该代码存在于mybatis.spring.boot.autoconfigure包下。

    //MybatisAutoConfiguration
     @Bean
      @ConditionalOnMissingBean
      public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
          return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
          return new SqlSessionTemplate(sqlSessionFactory);
        }
      }
    

    1.2 创建DefaultSqlSession

    创建DefaultSqlSession的流程图
    )
    创建DefaultSqlSession的过程首先是调用接口的xxx方法(cardMapper.selectCardById(1)),接着就是MapperProxy对象的invoke方法其实在mybatis 接口依赖注入源码分析就简单的展示了MapperProxy的源码,其实Spring帮我们的接口依赖注入对象就是MapperProxy对象。当我们调用接口中的xxx方法就直接调用代理类MapperProxy中的invoke方法。
    @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       ....
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // sqlSession 就是SqlSessionTemplate,执行sqlSessionTemplate中对应的方法。
        return mapperMethod.execute(sqlSession, args);
      }
    

    SqlSessionTemplate调用select方法的时候,就是sqlSessionProxy调用对应的方法,接着就是调用SqlSessionInterceptorinvoke方法。
    在上没有展示SqlSessionInterceptorinvoke方法,这里讲下。以下段代码主要分三部分。

    1. 创建SqlSession
    2. 执行SqlSession对应的方法。
    3. 关闭SqlSession
    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()
              sqlSession.commit(true);
            }
            return result;
          } catch (Throwable t) {
          
          } finally {
            if (sqlSession != null) {
              closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
          }
        }
      }
    

    二、获取SQL

    mybatis 解析xml流程中介绍了类似<select/>是以何种形式保存在Configuration对象中。以下是关键代码,MappedStatement表示<select/>等节点,ms.getId()是接口中的包名+类名+方法名(test.CardMapper.select)。因此现在SQL是以MappedStatement形式存在ConfigurationMap中,key为包名+类名+方法名,所以只要知道执行方法的key(包名+类名+方法名)就可以获取SQL

    #Configuration
     protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("xxxxx");
    
    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
      }
    

    其实当你调用接口的某个方法的时候就已经知道这个key(包名+类名+方法名)是什么了,以下是mybatis组装key的代码。

     private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
            Class<?> declaringClass, Configuration configuration) {
            //组装id key    
          String statementId = mapperInterface.getName() + "." + methodName;
          if (configuration.hasStatement(statementId)) {
            return configuration.getMappedStatement(statementId);
          } else if (mapperInterface.equals(declaringClass)) {
            return null;
          }
          for (Class<?> superInterface : mapperInterface.getInterfaces()) {
            if (declaringClass.isAssignableFrom(superInterface)) {
              MappedStatement ms = resolveMappedStatement(superInterface, methodName,
                  declaringClass, configuration);
              if (ms != null) {
                return ms;
              }
            }
          }
          return null;
        }
      }
    

    以下是DefaultSqlSession执行查询代码,具体解释见代码。

    @Override
      public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        try {
        
          //从map中获取MappedStatement(<select/>),statement就是包名+类名+方法名
          MappedStatement ms = configuration.getMappedStatement(statement);
          // 执行器进行查询
          executor.query(ms, wrapCollection(parameter), rowBounds, handler);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    

    在流程图上添加操作。


    image.png

    三、 走缓存 or 数据库

    有了SQL之后,接下来是考虑走缓存还是走数据库。首先mybatis有一级缓存PerpetualCache以及二级缓存。

    dd 。这里一级缓存的存活时间与SqlSession的何时关闭有关,而SqlSession的关闭与事务关联。如果外层没有事务,则一次查询后,缓存随着SqlSession的关闭而被清除。如果外层有事务的话,SqlSession查询一次后不会关闭,mybatis会继续将session保留在SessionHolder上,并且存活时间延迟到外层事务完成后关闭。
      SqlSessionUtils
      
      public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
        notNull(session, NO_SQL_SESSION_SPECIFIED);
        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
        if ((holder != null) && (holder.getSqlSession() == session)) {
            // 持有减一
            holder.released();
        } else {
          //关闭session
          session.close();
        }
      }
    SqlSessionUtils#SqlSessionSynchronization
    事务完成后关闭
     public void afterCompletion(int status) {
          if (this.holderActive) {
            // afterCompletion may have been called from a different thread
            // so avoid failing if there is nothing in this one
    
            TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
            this.holderActive = false;
            this.holder.getSqlSession().close();
          }
          this.holder.reset();
        }
    

    一级缓存是内存中的缓存,对象是PerpetualCache,在调用Executor构造函数的时候就会初始化一级缓存。

    ## BaseExecutor
      protected BaseExecutor(Configuration configuration, Transaction transaction) {
      ...
        //一级缓存
        this.localCache = new PerpetualCache("LocalCache");
      ...
      }
    
    image.png

    接下来贴一段代码查询代码。

    ## BaseExecutor
    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.");
        }
        // 相当于<select flushCache=true/>
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          // 清除缓存
          clearLocalCache();
        }
        List<E> list;
        try {
          queryStack++;
          // 查询缓存
          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();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
    

    继续添加代码流程。<select flushCache=true/>这个清除缓存(一级&二级缓存)

    image.png

    四 走数据库

    走数据库就要涉及到Connection的管理以及参数解析。

    4.1 Connection

    mybatis中是由org.apache.ibatis.transaction.Transaction管理Connection的生命周期(creation, preparation, commit/rollback and close),如果你整合了Spirng,那么具体的类是org.mybatis.spring.transaction.SpringManagedTransaction,但是SpringManagedTransaction本身不会直接负责Connection的创建和关闭,他向外提供Connection的创建和关闭功能都是由javax.sql.DataSource负责,比如创建连接、维护空闲连接池、活跃连接池等等都是由DateSource管理。由于这个DateSource是一个顶级接口,因此这提供了一个很好的扩展性,你可以选择自己喜欢的DateSource(c3po,druid等),由它来管理你的Connection
    Executor主要就是依靠Transaction来进行Connection的管理的,Executor的构造函数中由一个参数就是Transaction,那么这个Transaction是来源哪里?
    答案是从Environment中获取,以下是Environmment的三个属性。在org.mybatis.spring.SqlSessionFactoryBean创建Environmment的时候,如果没有TransactionFactory就默认给一个SpringManagedTransactionFactory对象并将他传给Environmment的构造函数

    #org.mybatis.spring.SqlSessionFactoryBean
     if (this.transactionFactory == null) {
          this.transactionFactory = new SpringManagedTransactionFactory();
        }
    
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    

    SpringManagedTransactionFactory生产的实例对象就是SpringManagedTransaction。因此ExecutorTransaction是来源自Environmment的属性对象TransactionFactory

    ##Environment
      private final String id;
      // 用于创建Transaction
      private final TransactionFactory transactionFactory;
      private final DataSource dataSource;
    

    4.2 参数解析

    mybatis解析参数主要是通过ParameterHandlerParameterHandler是一个接口,只是定义了解析参数的两个方法getParameterObject以及setParametersmybatis默认提供了DefaultParameterHandlerDefaultParameterHandler将入参一一对应到sql上,以下是参数解析的主要的代码。

    ##DefaultParameterHandler
      // 这里保存了<JavaType, TypeHandler>的对应关系
      private final TypeHandlerRegistry typeHandlerRegistry;
      // <select/>
      private final MappedStatement mappedStatement;
      // xxxMapper.select()的入参对象
      private final Object parameterObject;
      //
      private final BoundSql boundSql;
      private final Configuration configuration;
     @Override
      public void setParameters(PreparedStatement ps) {
        // 入参都会按照sql的参数顺序一一的放入parameterMappings 里面。
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          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)) { // issue #448 ask first for additional params
                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);
              }
               // 获取参数的类型处理器,比如string 对应StringTypeHandler
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              try {
                // 设置参数
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException e) {
    
              } catch (SQLException e) {
    
              }
            }
          }
        }
      }
    

    五、执行SQL

    执行SQL是通过StatementHandler执行,当然这是个接口,具体的操作由子类去实现。

    image.png
    RoutingStatementHandler: 仅仅是选择让哪个StatementHandler去执行SQL,下面是代码是RoutingStatementHandler的构造函数,里面根据StatementType选择不同的StatementHandler,而StatementType在写<select statementType="PREPARED"/>类似的语句的时候就已经决定了是什么类型的,默认是PREPARED
    #RoutingStatementHandler
      private final StatementHandler delegate;
    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, 
    RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
        switch (ms.getStatementType()) {
          case STATEMENT:
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case PREPARED:
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case CALLABLE:
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
      }
    

    总结

    两条路:
    SqlSession->获取SQL->走缓存
    SqlSession->获取SQL->走数据库->管理连接->参数解析->执行SQL

    相关文章

      网友评论

          本文标题:Mybatis 执行Sql流程

          本文链接:https://www.haomeiwen.com/subject/pzkjnqtx.html