• MyBatis框架原理2:SqlSession运行过程


    获取SqlSession对象

    SqlSession session = sqlSessionFactory.openSession();
    

    首先通过SqlSessionFactory的openSession方法获取SqlSession接口的实现类DefaultSqlSession对象。

    public interface SqlSessionFactory {
    
      SqlSession openSession();
    
      SqlSession openSession(boolean autoCommit);
      SqlSession openSession(Connection connection);
      SqlSession openSession(TransactionIsolationLevel level);
    
      SqlSession openSession(ExecutorType execType);
      SqlSession openSession(ExecutorType execType, boolean autoCommit);
      SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
      SqlSession openSession(ExecutorType execType, Connection connection);
    
      Configuration getConfiguration();
    
    }
    

    SqlSessionFactory接口提供一系列重载的openSession方法,其参数如下:

    • boolean autoCommit:是否开启JDBC事务的自动提交,默认为false。
    • Connection:提供连接。
    • TransactionIsolationLevel:定义事务隔离级别。
    • ExecutorType:定义执行器类型。

    DefaultSqlSessionFactory对象调用覆写的openSession方法:

    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }
    

    得到一个定义了ExecutorType为configuration的默认执行器SIMPLE,事务隔离级别为null,JDBC事务自动提交为false的DefaultSqlSession对象。

    获取MapperProxy代理对象

    有了DefaultSqlSession对象,以查询一条数据为例,来看一下整个处理过程。

    For example:

    SqlSession session = sqlSessionFactory.openSession();
    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectBlog(101);
    } finally {
      session.close();
    }
    

    MyBatis时序图:

    根据MyBatis文档推荐的方法,调用Mapper接口中的方法实现对数据库的操作,上述例子中根据blog ID获取Blog对象。
    通过DefaultSqlSession对象的getMapper方法获取的是一个MapperProxy代理对象,这也是Mapper接口不用实现类的原因。当调用BlogMapper中的方法时,由于BlogMapper是一个JDK动态代理对象,它会运行invoke方法,代码如下:

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          //判断代理对象是否是一个类
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        //生成MapperMethod对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //执行execute方法
        return mapperMethod.execute(sqlSession, args);
      }
    
      private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
          mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
          methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
      }
    ...
    

    invoke方法判断代理的对象是否是一个类,由于代理对象是一个接口,所以通过cachedMapperMethod生成一个MappedMethod对象,然后执行execute方法,execute方法代码如下:

    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;
            } 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);
            }
            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;
      }
    

    因为这里是根据ID查询一个对象,所以最终调用了DefaultSqlSession的selectOne方法,selectOne方法又调用自身selectList方法,最终将查询操作委托给Executor:

    @Override
      public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
          return list.get(0);
        } else if (list.size() > 1) {
          throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
          return null;
        }
      }
    
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
      }
    
      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          //根据id获取MappedStatement对象
          MappedStatement ms = configuration.getMappedStatement(statement);
          //wrapCollection方法处理集合参数
          //委托Exector执行SQL
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    

    Executor


    Executor在MyBatis加载全局配置文件时初始化,我们可以在全局配置文件settings元素中配置Executor类型,MyBatis默认使用SimpleExecutor,如果开启了二级缓存,则再用CachingExecutor进行包装。

    如果开启了二级缓存,SqlSession调用CachingExecutor执行器的query方法,先从二级缓存获取数据,当无法从二级缓存获取数据时,则委托给BaseExecutor的子类进行操作,CachingExecutor执行过程代码如下:

     public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        //判断是否有二级缓存  
        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);
            //如果二级缓存没有数据则委托给BaseExcutor的子类进行操作
            if (list == null) {
              list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        //如果没有二级缓存则委托给BaseExcutor的子类进行操作
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    

    MyBatis默认使用SimpleExecutor,调用父类BaseExecutor的query方法:

    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;
          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;
      }
    

    最后,queryFromDatabase方法则调用SimpleExecutor的doQuery方法,通过Configuration构建StatementHandler对SQL和参数编译,parameterize()方法通过ParameterHandler对参数进行设置,使用TypeHandler转换参数类型,执行查询后再通过ResultSetHandler封装结果并返回,其方法代码如下:

     public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          //根据Configuration构建StatementHandler
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          //编译SQL
          stmt = prepareStatement(handler, ms.getStatementLog());
          //ResultSetHandler处理结果,并返回处理后的结果
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        //处理SQL参数
        handler.parameterize(stmt);
        return stmt;
      }
    

    通过以上流程发现,MyBatis核心工作实际上是由Executor、StatementHandler、ParameterHandler和ResultSetHandler四个接口完成的,掌握这四个接口的工作原理,对理解MyBatis底层工作原理有很大帮助。

    StatementHandler


    StatementHandler接口设计采用了适配器模式, 实现类RoutingStatementHandler根据上下文来选择适配器生成相应的StatementHandler。三个适配器分别是SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。StatementHandler初始化过程如下:

    //在Configuration中构建StatementHandler
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    
    

    RoutingStatementHandler构建过程:

    public class RoutingStatementHandler implements StatementHandler {
    
      private final StatementHandler delegate;
    
      public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        //根据我们映射配置文件中的SQL选择适配器,MyBatis默认使用PreparedStatement
        switch (ms.getStatementType()) {
          case STATEMENT:
            //SimpleStatementHandler对应JDBC中的Statement
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case PREPARED:
            //PreparedStatementHandler对应JDBC中PreparedStatement
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case CALLABLE:
            //CallableStatementHandler对应JDBC中CallableStatement
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
    
      }
    

    通常我们使用PreparedStatementHandler,调用父类prepare方法,对SQL预编译:

     @Override
      public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
          statement = instantiateStatement(connection);
          setStatementTimeout(statement, transactionTimeout);
          setFetchSize(statement);
          return statement;
        } catch (SQLException e) {
          closeStatement(statement);
          throw e;
        } catch (Exception e) {
          closeStatement(statement);
          throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
        }
      }
    

    通过PreparedStatementHandler的instantiateStatement方法可以看到,这里实际就是在调用JDBC中的prepareStatement方法进行SQL的预编译,query方法则是调用JDBC的execute方法来执行编译好的SQL和返回结果:

     @Override
      protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
          String[] keyColumnNames = mappedStatement.getKeyColumns();
          //JDBC方法创建PrepareStatement对象
          if (keyColumnNames == null) {
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
          } else {
                    return connection.prepareStatement(sql, keyColumnNames);
          }
        } else if (mappedStatement.getResultSetType() != null) {
          return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        } else {
          return connection.prepareStatement(sql);
        }
      }
      
      @Override
      public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        //JDBC中PreparedStatement对象的execute()方法执行SQL
        ps.execute();
        //ResultSetHandler对结果进行封装和返回
        return resultSetHandler.<E> handleResultSets(ps);
      }
    
    

    ParameterHandler

    ParameterHandler接口作用就是设置预编译SQL的参数:

    public interface ParameterHandler {
    
      Object getParameterObject();
    
      void setParameters(PreparedStatement ps)
          throws SQLException;
    
    }
    

    接口提供getParameterObject和setParameters方法,前者作用获取参数对象,后者作用是设置预编译SQL的参数,由实现类DefaultParameterHandler执行,使用TypeHandler将参数对象类型转换成jdbcType,完成预编译SQL的参数设置。

      @Override
      public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        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);
              }
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              //设置参数类型为jdbcType
              try {
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              } catch (SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
      }
    

    ResultSetHandler

    ResultSetHandler接口根据配置文件中定义的规则将结果映射成相应对象,接口定义了三个方法,源码如下:

    public interface ResultSetHandler {
      // 处理结果集,映射成对应的对象集合
      <E> List<e> handleResultSets(Statement stmt) throws SQLException;
      // 处理结果集,返回相应的游标对象
      <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
      // 处理储存过程输出参数
      void handleOutputParameters(CallableStatement cs) throws SQLException;
    }
    

    ResultSetHandler接口的具体实现是DefaultResultSetHandler,我们通过SELECT语句执行得到的结果集由其handleResultSets方法处理,方法如下:

     @Override
      public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
        // 生成ArrayList用于保存结果集映射的对象
        final List<Object> multipleResults = new ArrayList<Object>();
    
        int resultSetCount = 0;
        // 获取第一个ResultSet对象
        ResultSetWrapper rsw = getFirstResultSet(stmt);
        // 获取MyBatis初始化时解析映射器配置文件中的resultMap节点生成的ResultMap对象
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        // 验证resultMap是否存在
        validateResultMapsCount(rsw, resultMapCount);
        while (rsw != null && resultMapCount > resultSetCount) {
          ResultMap resultMap = resultMaps.get(resultSetCount);
          // 根据映射规则将查询结果映射成ResultMap并放入multipleResults集合中
          handleResultSet(rsw, resultMap, multipleResults, null);
          // 获取下一个结果
          rsw = getNextResultSet(stmt);
          cleanUpAfterHandlingResultSet();
          //递增直到所有结果映射完成
          resultSetCount++;
        }
        //此处省略resultSets多结果集处理方法
        //...
        return collapseSingleResultList(multipleResults);
      }
    

    通过对4个核心功能接口作用的简单分析,我们对MyBatis底层工作原理就有了初步认识,但是要进一步深入理解MyBatis框架原理还需要对MyBatis其他组件的原理进行探究。

  • 相关阅读:
    PHP迭代生成器---yield
    array_chunk — 将一个数组分割成多个
    php array_change_key_case
    PHP trait介绍
    mysql视图
    mysql常见内置函数
    MySQL表复制
    二分查找算法(折半查找算法)
    使用SplFixedArray创建固定大小的数组
    Frameset Example
  • 原文地址:https://www.cnblogs.com/abcboy/p/9656302.html
Copyright © 2020-2023  润新知