【mybatis】SqlSession运行过程——SqlSession的四大对象

在映射器Mapper的动态代理中,了解到mybatis是如何通过Mapper接口的全限定名和方法名来运行SQL的,接下来我们要看看SqlSession底层是怎么运行SQL的。
实际上,SqlSession的执行过程是通过Executor、StatementHandler、ParameterHandler和ResultSetHandler来完成数据库操作和结果返回的,这就是SqlSession的四大对象。

  • Executor:执行器,由它调度StatementHandler、ParameterHandler和ResultSetHandler等来执行对应的SQL。
  • StatementHandler:使用数据库的Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的
  • ParameterHandler:用来处理SQL参数。
  • ResultSetHandler:进行数据集(ResultSet)的封装返回处理的。

1、Executot——执行器

1.1、执行器介绍

Executor是一个执行器,SqlSession是一个门面,真正干活的是执行器,它是一个真正执行Java与数据库交互的对象。
mybatis中有3种执行器,可以在mybaits配置文件中的defaultExecutorType属性进行选择,对于spring boot项目则在application配置文件中配置mybatis.configuration.default-executor-type属性。

1.2、执行器的种类

  • SIMPLE——简易执行器,没有什么特别的,默认执行器。
  • REUSE——一种能够执行重用预处理语句的执行器。
  • BATCH——执行器重用语句和批量更新,批量专用的执行器。

1.3、执行器的加载

在Configuration类中有如下方法,该方法根据配置的执行器类型去确定创建哪一种Executor。

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
     
    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) {
     
	//缓存用CachingExecutor进行包装Executor
      executor = new CachingExecutor(executor);
    }
	//在运用插件时,拦截Executor
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

上述代码中的interceptorChain.pluginAll(executor);是运行插件的关键,它拦截Executor,并构建一层层的动态代理对象,可以修改在调度真实的Executor方法之前执行配置插件的代码,这个就是插件的原理


1.4、执行器是如何执行SQL的?

在映射器Mapper的动态代理中,我们知道最后就是通过SqlSession对象去运行对象的SQL。通过跟踪代码,在Mapper的代理对象(即MapperProxy的实例)中的invoke()方法中,最后是执行了mapperMethod.execute(sqlSession, args);,而在execute()方法中,是采用了命令模式跳转到需要的方法中,在上文中,我们看了executeForMany()方法,其中有下面这段代码:

	if (method.hasRowBounds()) {
     
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
     
      result = sqlSession.selectList(command.getName(), param);
    }

可以看到,最终就是通过SqlSession对象去运行对象的SQL,在单线程环境下,SqlSession的实现类是DefaultSqlSession,它的selectList()方法如下:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
     
    try {
     
      MappedStatement ms = configuration.getMappedStatement(statement);
      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的query()方法,定位到Executor的实现类BaseExecutor,代码如下:

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

跟踪list = 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 {
     
	  //由Executor的三种类型实现
      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;
  }

上面代码中的doQuery(ms, parameter, rowBounds, resultHandler, boundSql);是由Executor的三种类型来实现的,默认是SIMPLE,即SimpleExecutor,如下:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     
    Statement stmt = null;
    try {
     
	  //从该映射器节点MappedStatement中获取配置信息Configuration
      Configuration configuration = ms.getConfiguration();
	  //根据Configuration构建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
	  //对SQL编译和参数进行初始化
      stmt = prepareStatement(handler, ms.getStatementLog());
	  //使用StatementHandler的query()方法,把ResultHandler传递进去
      return handler.query(stmt, resultHandler);
    } finally {
     
      closeStatement(stmt);
    }
  }
  
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
     
    Statement stmt;
    Connection connection = getConnection(statementLog);
	//调用StatementHandler的prepare()进行了预编译和基础的设置
    stmt = handler.prepare(connection, transaction.getTimeout());
	//通过StatementHandler的parameterize()来设置参数
    handler.parameterize(stmt);
    return stmt;
  }

根据上面代码,可以看到最终的操作落到StatementHandler上。


2、StatementHandler——数据库会话器

2.1、StatementHandler介绍

数据库会话器就是专门处理数据库会话的,在Configuration中,mybatis通过如下方式生成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;
  }

  //对象适配器,它的作用是给3个接口对象的使用提供一个统一且简易的适配器
  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());
    }

  }
很显然,mybatis是通过RoutingStatementHandler的对象来创建真实对象的,RoutingStatementHandler不是真实的服务对象,它是通过**适配器模式**来找到对应的StatementHandler来执行的。
在mybaits中,RoutingStatementHandler分为3种:
SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler,他们分别对应的是JDBC的Statement、PreparedStatement(预编译处理)和CallableStatement(存储过程处理)。

2.2、StatementHandler的执行过程(以PreparedStatementHandler为例)

我们在Executor部分说到,Executor在执行查询时(如doQuery()方法)会执行StatementHandler的prepare、parameterize和query方法。其中PreparedStatementHandler的prepare方法如下:

public abstract class BaseStatementHandler implements StatementHandler {
     
	//......
	@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
     
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
     
	  //对SQL进行预编译,做一些基础配置,由不同的StatementHandler的实现
      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);
    }
  }
	//...
}

接着,通过parameterize()方法设置参数,如下:

  @Override
  public void parameterize(Statement statement) throws SQLException {
     
   //显然使用了ParameterHandler
    parameterHandler.setParameters((PreparedStatement) statement);
  }

最后执行query()查询方法——执行SQL返回结果,如下:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     
   //java.sql.PreparedStatement 与数据库交互
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
	//包装结果集ResultSet
    return resultSetHandler.handleResultSets(ps);
  }

【面试题】一条查询SQL的执行过程?

Executor先调用StatementHandler的prepare()方法预编译SQL,同时设置一些基本运行的参数。
然后用parameterize()方法启用ParameterHandler设置参数,完成预编译,执行查询,update()也是这样的。
如果是查询,mybatis会使用ResultSetHandler封装结果返回给调用者。

3、ParameterHandler——参数处理器

  • mybatis通过ParameterHandler对预编译语句进行参数设置,它的作用是完成对预编译参数的设置。
  • mybatis为ParameterHandler提供了一个实现类DefaultParameterHandler。
  • 设置参数过程中,是从parameterObject对象中取到参数,然后使用TypeHandler转换参数,如果有设置,那么它会根据签名注册的TypeHandler对参数进行处理。
  • TypeHandler是在mybatis初始化时,注册在Configutation里面的,需要时就可以直接拿来用了。

4、ResultSetHandler——结果处理器

  • ResultSetHandler是组装结果集返回的。
  • mybatis为ResultSetHandler提供了一个实现类DefaultResultSetHandler,在默认情况下都是通过这个类进行处理的。
  • 它涉及使用JAVASSIST(或者CGLIB)作为延迟加载。
  • 然后通过TypeHandler和ObjectFactory进行组装结果再返回。

5、总结:SqlSession内部运行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCYTHBIz-1586362133085)(http://ver-oss.oss-cn-beijing.aliyuncs.com/blog/image/20191226/20191226_03285195.png “SqlSession内部运行”)]

  • prepare:预编译SQL。
  • parameterize:设置参数。
  • query/update:执行SQL。

你可能感兴趣的:(Java,mybatis,mybatis,java)