MyBatis(四) sql执行流程

1.MyBatis(三) xml文件解析流程 动态SQL解析,中介绍了MyBatis SQL的解析过程。那么MyBatis执行流程中就涉及到具体sql生成的过程了。首先看下SqlSession的获取

《MyBatis(四) sql执行流程》 MyBatis源码分析时序图.png

1.1 首先调用SqlSessionFactoryBuilder的build方法来初始化加载配置文件这里前面已经分析过了,此处给返回SqlSessionFactory对象

sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

1.2 通过DefaultSqlSessionFactory的openSession方法,实际调用的是它的openSessionFromDataSource方法,这里返回SqlSession ,具体来说是返回的是SqlSession 的实现类DefaultSqlSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
      return new DefaultSqlSession(configuration, executor);
    } 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();
    }
  }

2.SqlSession与SqlSessionFactorySqlSession通过SqlSessionFactory的opensession方法返回的

  private final static SqlSessionFactory sqlSessionFactory;
    static {
        String resource = "mybatisConfig.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            System.out.println(e.getMessage());

        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }
 SqlSession sqlSession = sqlSessionFactory.openSession();
VmanBomDao vmanBomDao = sqlSession.getMapper(VmanBomDao.class);
int i = vmanBomDao.insertVmanBomBean(vmanBomBean);

当我们得到SqlSession通过getMapper方法来获取dao最后通过dao来执行相应的数据库代码,其实这里获取是Mapper也就是dao的代理类对象,具体流程如下:

《MyBatis(四) sql执行流程》 MyBatis源码分析时序图2.png

2.1 首先通过
SqlSession这个具体实现类是
DefaultSqlSession的getMapper来获取代理对象,

  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

2.2 然而DefaultSqlSession什么都不做,将mapper的获取交给了Configuration了。Configuration的getMapper方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

2.3 接着又被转交给MapperRegistry对象的getMapper方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

2.4 MapperProxyFactory的newInstance方法,我们可以看出这里使用了动态代理机制,而代理类就是MapperProxy

 protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

3 MapperProxy
3.1 sql真正调用开始的地方,MapperProxy的invoke方法最终会被代理对象调用

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//因为所有的类都继承自Object对象,如果没有此处的代码则tostring等方法调用都只会返回null
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
//处理Mapper方法
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

3.2 MapperMethod 处理mapper接口的方法,调用 mapperMethod.execute(sqlSession, args);此处根据不同的sql语句类型来处理调用sqlSession对应的方法,而这个SqlCommandType从哪里来,答案就在前面的xml解析中的MappedStatement对象,而MappedStatement又保存在Configuration中,前面说过Configuration是MyBatis这个调度配置中心,详情可以MapperMethod构造方法中查看SqlCommand对象。这里就不展开

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
  }

4.回到SqlSession的实现类DefaultSqlSession,只分析一个方法其他的都类似,MyBatis(三) xml文件解析流程 动态SQL解析文中保存了各个SqlNode都在等待着MixedSqlNode的apply方法。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

4.1 Executor: SQL的执行器。

《MyBatis(四) sql执行流程》 Executor.png

<p>

1.BaseExecutor: 作为SQL的执行器基类
2.ReuseExecutor:继承自BaseExcutor,ReuseExecutor顾名思义就是重复使用执行,其定义了一个Map<String, Statement>,将执行的sql作为key,将执行的Statement作为value保存,这样执行相同的sql时就可以使用已经存在的Statement,就不需要新创建了
3.BatchExecutor:批量处理器
4.SimpleExecutor:简单处理器,也是默认的sql执行器
5.CachingExecutor: 这个执行器跟二级缓存有关,当开启二级缓存是就会使用这个SQL执行器。

4.2 SimpleExecutor默认sql执行器的创建在Configuration中的newExecutor方法中

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

4.3SimpleExecutor默认sql执行器的query方法, 此方法来自他的基类BaseExecutorSimpleExecutor并没有重写该方法,但是此处执行BaseExecutor此方法的意义就是sql的生成。其中**BoundSql **类中包含了sql

  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);
 }
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++;
//进行查询是 localCache一级缓存中获取
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {

        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
//如果一级缓存中为null则执行查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

这里稍微提下Mybatis一级缓存,上面方法中用到了一级缓存,后面会说到。
4.3.1**BoundSql **

public class BoundSql {

  private String sql;
  private List<ParameterMapping> parameterMappings;
  private Object parameterObject;
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;
}

4.3.2 BoundSql 通过SqlSource(实现类DynamicSqlSource)的getBoundSql方法获取,取得了BoundSql也就取得了动态生成后的sql语句就可以交给Executor执行了。

public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
//这里的rootSqlNode就是前文中的MixedSqlNode
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

4.3.3
XMLScriptBuilder中的此方法中就已经保存到了SqlSource MixedSqlNode 会根据SqlNode的不同执行不同类别的sqlNode的apply方法正真根据参数生成可执行sql语句

  public SqlSource parseScriptNode() {
    List<SqlNode> contents = parseDynamicTags(context);
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    return sqlSource;
  }

4.4 BaseExecutor的 queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

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

4.5 doquery方法,BaseExecutor的doquery为抽象方法,所以具体实现在它的非抽象子类SimpleExecutor默认sql执行器中

 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
//MappedStatement 来自Configuration,同时MappedStatement 中也包含Configuration,通过MappedStatement 来获取Configuration对象
      Configuration configuration = ms.getConfiguration();
//StatementHandler 获取
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

5 . StatementHandler 介绍

《MyBatis(四) sql执行流程》 StatementHandler.png

<p>

1.SimpleStatementHandler: 其实就是对jdbc Statement的封装
2.PreparedStatementHandler: 其实就是对jdbc PreparedStatement的封装,对sql进行预编译,防止sql注入
3.CallableStatementHandler: 其实就是对jdbc 的CallableStatement的封装,对调用存储过程时使用
4 . RoutingStatementHandler:作为其他Statement的调度中心

5.1MappedStatement中默认设置StatementType为PREPARED

  mappedStatement.statementType = StatementType.PREPARED;

5.2 当追踪查询语句执行时返现MappedStatement的创建在 Configuration中的newStatementHandler方法中调用 RoutingStatementHandler的构造方法中创建的

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

  }

5.3 sql执行StatementHandler的query方法,以 PreparedStatementHandler为例,这里是不是很熟悉,这不就是jdbc的操作嘛


  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
    原文作者:PPB
    原文地址: https://www.jianshu.com/p/a6b5e066c3d9
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞