返回介绍

6.3 SqlSession 运行过程

发布于 2025-04-26 13:08:34 字数 17121 浏览 0 评论 0 收藏

SqlSession 的运行过程是本章的重点和难点,也是整个 MyBatis 最难以理解的部分。SqlSession 是一个接口,使用它并不复杂。我们构建 SqlSessionFactory 就可以轻易地拿到 SqlSession 了。SqlSession 给出了查询、插入、更新、删除的方法,在旧版本的 MyBatis 或 iBatis 中常常使用这些接口方法,而在新版的 MyBatis 中我们建议使用 Mapper,所以它就是 MyBatis 最为常用和重要的接口之一。

但是,SqlSession 内部可没有那么容易,因为它的内部实现相当复杂,在后面我们会详细讨论其实现方式。本章的每一节都是承上启下的,我们需要一步步地掌握本章内容,一旦脱节你很容易迷失在过程中。

6.3.1 映射器的动态代理

Mapper 映射是通过动态代理来实现的,我们首先来看看代码清单 6-7。

代码清单 6-7:MapperProxyFactory.java

public class MapperProxyFactory<T> {

.......

  @SuppressWarnings("unchecked")
  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);
  }

}

这里我们可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象(占位)。而代理的方法则被放到了 MapperProxy 类中。

我们探讨一下 MapperProxy 的源码,如代码清单 6-8 所示。

代码清单 6-8:MapperProxy.java

public class MapperProxy<T> implements InvocationHandler, Serializable {
......
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
......
}

上面运用了 invoke 方法。一旦 mapper 是一个代理对象,那么它就会运行到 invoke 方法里面,invoke 首先判断它是否是一个类,显然这里 Mapper 是一个接口不是类,所以判定失败。那么就会生成 MapperMethod 对象,它是通过 cachedMapperMethod 方法对其初始化的,然后执行 execute 方法,把 sqlSession 和当前运行的参数传递进去。

让我们看看这个 execute 方法的源码,如代码清单 6-9 所示。

代码清单 6-9:MapperProxy.java

  public class MapperMethod {  
    private final SqlCommand command;  
private final MethodSignature method;
......
    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 if (SqlCommandType.FLUSH == command.getType()) {  
          result = sqlSession.flushStatements();  
      } 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;  
    }  
   ........  
    //方法还是很多的,我们不需要全看,看一个常用的查询返回多条记录的方法即可
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {  
      List<E> result;  
      Object param = method.convertArgsToSqlCommandParam(args);  
      if (method.hasRowBounds()) {  
        RowBounds rowBounds = method.extractRowBounds(args);  
        result = sqlSession.<E>selectList(command.getName(), param, rowBounds);  
      } else {  
        result = sqlSession.<E>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;  
    }  
    .......  
   }  

MapperMethod 采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中,我们不需要全部明白。我们可以看到里面的 executeForMany 方法,再看看它的实现,实际上它最后就是通过 sqlSession 对象去运行对象的 SQL。

至此,相信大家已经了解了 MyBatis 为什么只用 Mappper 接口便能够运行 SQL,因为映射器的 XML 文件的命名空间对应的便是这个接口的全路径,那么它根据全路径和方法名便能够绑定起来,通过动态代理技术,让这个接口跑起来。而后采用命令模式,最后还是使用 SqlSession 接口的方法使得它能够执行查询,有了这层封装我们便可以使用接口编程,这样编程就更简单了。

6.3.2 SqlSession 下的四大对象

我们已经知道了映射器其实就是一个动态代理对象,进入到了 MapperMethod 的 execute 方法。它经过简单判断就进入了 SqlSession 的删除、更新、插入、选择等方法,那么这些方法如何执行呢?这是我们需要关心的问题,也是正确编写插件的根本。

显然通过类名和方法名字就可以匹配到我们配置的 SQL,我们不需要去关心这些细节,我们关心的是设计框架。Mapper 执行的过程是通过 Executor、StatementHandler、ParameterHandler 和 ResultHandler 来完成数据库操作和结果返回的。

  • Executor 代表执行器,由它来调度 StatementHandler、ParameterHandler、ResultHandler 等来执行对应的 SQL。

  • StatementHandler 的作用是使用数据库的 Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用。

  • ParameterHandler 用于 SQL 对参数的处理。

  • ResultHandler 是进行最后数据集(ResultSet)的封装返回处理的。

下面我们逐一分析讲解这四个对象的生成和运作原理。到这里我们已经来到了 MyBatis 的底层设计,对 Java 语言基础不牢的读者来说,这将是一次挑战。

6.3.2.1 执行器

执行器(Executor)起到了至关重要的作用。它是一个真正执行 Java 和数据库交互的东西。在 MyBatis 中存在三种执行器。我们可以在 MyBatis 的配置文件中进行选择,具体请参看 3.2 节关于 setting 元素的属性 defaultExecutorType 的说明。

  • SIMPLE,简易执行器,不配置它就是默认执行器。

  • REUSE,是一种执行器重用预处理语句。

  • BATCH,执行器重用语句和批量更新,它是针对批量专用的执行器。

它们都提供了查询和更新的方法,以及相关的事务方法。这些和其他框架并无不同,不过我们要了解一下它们是如何构造的,让我们来看看 MyBatis 如何创建 Executor,如代码清单 6-10 所示。

代码清单 6-10:执行器生成

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) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

如同描述的一样,MyBatis 将根据配置类型去确定你需要创建三种执行器中的哪一种,在创建对象后,它会去执行下面这样一行代码。

interceptorChain.pluginAll(executor);

这就是 MyBatis 的插件,这里它将为我们构建一层层的动态代理对象。在调度真实的 Executor 方法之前执行配置插件的代码可以修改。现在不妨先看看执行器方法内部,以 SIMPLE 执行器 SimpleExecutor 的查询方法作为例子进行讲解,如代码清单 6-11 所示。

代码清单 6-11:SimpleExecutor.java 执行器的执行过程

public class SimpleExecutor extends BaseExecutor {
 ......
    @Override
  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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      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);
    handler.parameterize(stmt);
    return stmt;
  }
......
}

显然 MyBatis 根据 Configuration 来构建 StatementHandler,然后使用 prepareStatement 方法,对 SQL 编译并对参数进行初始化,我们在看它的实现过程,它调用了 StatementHandler 的 prepare() 进行了预编译和基础设置,然后通过 StatementHandler 的 parameterize() 来设置参数并执行,resultHandler 再组装查询结果返回给调用者来完成一次查询。这样我们的焦点又转移到了 StatementHandler 上。

6.3.2.2 数据库会话器

顾名思义,数据库会话器(StatementHandler)就是专门处理数据库会话的,让我们先来看看 MyBatis 是如何创建 StatementHandler 的,再看 Configuration.java 生成会话器的地方,如代码清单 6-12 所示。

代码清单 6-12:创建 StatementHander

 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 对象,它实现了接口 StatementHandler。和 Executor 一样,用代理对象做一层层的封装,第 7 章我们会讨论它。

RoutingStatementHandler 不是我们真实的服务对象,它是通过适配模式找到对应的 StatementHandler 来执行的。在 MyBatis 中,StatementHandler 和 Executor 一样分为三种:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。它所对应的是 6.3.2.1 节讨论的三种执行器。

在初始化 RoutingStatementHandler 对象的时候它会根据上下文环境决定创建哪个 StatementHandler 对象,我们看看 RoutingStatementHandler 的源码,如代码清单 6-13 所示。

代码清单 6-13:RoutingStatementHandler 的源码

public class RoutingStatementHandler implements StatementHandler {

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

数据库会话器定义了一个对象的适配器 delegate,它是一个 StatementHandler 接口对象,构造方法根据配置来适配对应的 StatementHandler 对象。它的作用是给实现类对象的使用提供一个统一、简易的使用适配器。此为对象的适配模式,可以让我们使用现有的类和方法对外提供服务,也可以根据实际的需求对外屏蔽一些方法,甚至是加入新的服务。

我们现在以最常用的 PreparedStatementHandler 为例,看看 MyBatis 是怎么执行查询的。在讲解执行器时我们看到了它的三个主要的方法,prepare、parameterize 和 query,如代码清单 6-14 所示。

代码清单 6-14:BaseStatementHandler 的 prepare 方法

public abstract class BaseStatementHandler implements StatementHandler {
.......
    @Override
  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement);
      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);
    }
  }
......
}

instantiateStatement() 方法是对 SQL 进行了预编译。首先,做一些基础配置,比如超时,获取的最大行数等的设置。然后,Executor 会调用 parameterize() 方法去设置参数,它的方法如代码清单 6-15 所示。

代码清单 6-15:设置参数

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

这个时候它是调用 ParameterHandler 去完成的,6.3.2.3 节我们将讨论如何使用它,这里先学习 StatementHandler 的查询方法吧,如代码清单 6-16 所示。

代码清单 6-16:查询方法

public class PreparedStatementHandler extends BaseStatementHandler {
   ......
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
  ......
}

由于在执行前参数和 SQL 都被 prepare() 方法预编译,参数在 parameterize() 方法上已经进行了设置。所以到这里已经很简单了。我们只要执行 SQL,然后返回结果就可以了。执行之后我们看到了 ResultSetHandler 对结果的封装和返回。

到了这里我们就很清楚一条查询 SQL 的执行过程了。

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

这样我们就清楚了执行 SQL 的流程了,很多东西都已经豁然开朗,下面我们再讨论另外两个对象的使用,那就是 ParameterHandler 和 ResultSetHandler。

6.3.2.3 参数处理器

我们在 6.3.2.2 节中看到了 MyBatis 是通过参数处理器(ParameterHandler)对预编译语句进行参数设置的。它的作用是很明显的,那就是完成对预编译参数的设置。让我们先看它的定义,如代码清单 6-17 所示。

代码 6-17:ParameterHandler.java

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

其中,getParameterObject() 方法的作用是返回参数对象,setParameters() 方法的作用是设置预编译 SQL 语句的参数。

MyBatis 为 ParameterHandler 提供了一个实现类 DefaultParameterHandler,我们来看看 setParameters 的实现,如代码清单 6-18 所示。

代码 6-18:用参数处理器设置参数

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

我们可以看到它还是从 parameterObject 对象中取参数,然后使用 typeHandler 进行参数处理,这就和第 3 章的 typeHandler 配置一样,如果你有设置,那么它就会根据签名注册的 typeHandler 对参数进行处理。而 typeHandler 也是在 MyBatis 初始化的时候,注册在 Configuration 里面的,我们需要的时候可以直接拿来用。这样就完成了参数的设置。

6.3.2.4 结果处理器

有了 StatementHandler 的描述,我们知道它就是组装结果集返回的。我们再来看看结果处理器(ResultSetHandler)的接口定义,如代码清单 6-19 所示。

代码 6-19:结果处理器的接口定义

public interface ResultSetHandler {
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  void handleOutputParameters(CallableStatement cs) throws SQLException;
}

其中,handleOutputParameters() 方法是处理存储过程输出参数的,我们暂时不必管它,重点看一下 handleResultSets() 方法,它是包装结果集的。MyBatis 同样为我们提供了一个 DefaultResultSetHandler 类,在默认的情况下都是通过这个类进行处理的。这个实现有些复杂,它涉及使用 JAVASSIST 或者 CGLIB 作为延迟加载,然后通过 typeHandler 和 ObjectFactory 进行组装结果再返回,笔者就不详细论述了,因为我们需要改变它们的概率很小。

我们现在清楚了一个 SqlSession 通过 Mapper 运行方式的运行原理,而通过 SqlSession 接口的查询、更新等方法也是类似的。至此,我们已经明确 MyBatis 底层的 SqlSession 内的秘密,也了解了它的工作原理。这为我们学习插件的运行奠定了坚实的基础。

6.3.3 SqlSession 运行总结

SqlSession 的运行原理十分重要,它是插件的基础,这里我们对一次查询或者更新进行总结以加深对 MyBatis 内部运行的掌握。SqlSession 内部运行图,如图 6-4 所示。

160_0001

图 6-4 SqlSession 内部运行图

SqlSession 是通过 Executor 创建 StatementHandler 来运行的,而 StatementHandler 要经过下面三步。

  • prepared 预编译 SQL。

  • parameterize 设置参数。

  • query/update 执行 SQL。

其中 parameterize 是调用 parameterHandler 的方法去设置的,而参数是根据类型处理器 typeHandler 去处理的。query/update 方法是通过 resultHandler 进行处理结果的封装,如果是 update 的语句,它就返回整数,否则它就通过 typeHandler 处理结果类型,然后用 ObjectFactory 提供的规则组装对象,返回给调用者。这便是 SqlSession 执行的过程,我们清楚了四大对象是如何协作的,同时也更好地理解了 typeHandler 和 ObjectFactory 在 MyBatis 中的应用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。