MyBatis之解析及其运行原理

目录

1 构建SqlSessionFactory过程

1.1 构建Configuration

1.2 构建映射器的内部组成

2 SqlSession运行过程

2.1 构建SqlSession过程

2.2 映射器(Mapper)的动态代理

2.3 SqlSession下的四大对象

2.3.1 Executor——执行器

2.3.2 StatementHandler——数据库会话器

2.3.3 ParameterHandler——参数处理器

2.3.4 ResultSetHandler——结果处理器

2.4 SqlSession运行总结


上文介绍了MyBatis的动态SQL,感兴趣的可以查看《MyBatis之动态SQL》。本文讲解MyBatis的解析和运行原理。MyBatis的运行过程分为两大步:

  • 第1步,读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory;
  • 第2步,SqlSession的执行过程。相对而言,SqlSessionFactory的创建还算比较容易理解,而SqlSession的执行过程就不是那么简单了,它包括许多复杂的技术,要先掌握反射技术和动态代理技术,这是揭示MyBatis底层架构的基础。动态代理的相关知识可以查阅笔者的另一篇博客《动态代理概述》

1 构建SqlSessionFactory过程

SqlSessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心接口SqlSession,所以要先创建SqlSessionFactory,为此要提供配置文件和相关的参数。MyBatis是一个复杂的系统,它采用了建造者模式来创建SqlSessionFactory,在实际中可以通过SqlSessionFactoryBuilder来创建,其构建分为两步:

  • 第1步:通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件,读出所配置的参数,并将读取的内容存入org.apache.ibatis.session.Configuration类对象中。而Configuration类采用的是单例模式,几乎所有的MyBatis配置内容都会存放在这个单例对象中,以便后续将这些内容读出。
  • 第2步:使用Configuration对象来创建SqlSessionFactory。MyBatis中的SqlSessionFactory是一个接口,而不是一个实现类,为此MyBatis提供了一个默认的实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory。在大部分情况下都没有必要自己来创建新的SqlSessionFactory实现类。

上述构建过程用到了建造者模式,关于建造者模式的介绍和使用可以参考笔者的另一篇文章《设计模式之建造者模式》

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

看过笔者之前MyBatis相关文章的读者们对上面的代码应该不陌生,上述代码就是用来构建SqlSessionFactory的。build方法类似于重叠构造器的写法,代码如下:

public SqlSessionFactory build(InputStream inputStream)
  {
    return build(inputStream, null, null);
  }

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
  {
    try
    {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    }
    catch (Exception e)
    {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    }
    finally
    {
      ErrorContext.instance().reset();
      try
      {
        inputStream.close();
      }
      catch (IOException localIOException1) {}
    }
  }

可以看到,首先调用XMLConfigBuilder的构造器,初始化Configuration相关。XMLConfigBuilder的构造器代码如下:

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props)
  {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props)
  {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

然后调用XMLConfigBuilder的parse方法开始解析XML中内容并返回Configuration,代码如下:

public Configuration parse()
  {
    if (this.parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    this.parsed = true;
    parseConfiguration(this.parser.evalNode("/configuration"));
    return this.configuration;
  }
  
  private void parseConfiguration(XNode root)
  {
    try
    {
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    }
    catch (Exception e)
    {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

从源码中可以看到,它是通过一步步解析XML的内容得到对应的信息的,而这些信息正是我们在配置文件中配置的内容。这里我们拿typeHandler的解析过程来举例,其他配置内容的代码是相似的。typeHandlerElement方法代码如下:

private void typeHandlerElement(XNode parent)
    throws Exception
  {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName()))
        {
          String typeHandlerPackage = child.getStringAttribute("name");
          this.typeHandlerRegistry.register(typeHandlerPackage);
        }
        else
        {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null)
          {
            if (jdbcType == null) {
              this.typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              this.typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          }
          else {
            this.typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

配置的typeHandler都会被注册到typeHandlerRegistry对象中去,typeHandlerRegistry的定义是放在XMLConfigBuilder的父类BaseBuilder中,如下:

public abstract class BaseBuilder
{
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  
  public BaseBuilder(Configuration configuration)
  {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
  //do something...
}

Configuration的部分源码可以参考笔者之前的博客《设计模式之建造者模式》。从上述源码可以知道,typeHandlerRegistry对象实际就是Configuration的一个属性,所以可以通过Configuration拿到typeHandlerRegistry对象,进而拿到我们所注册的typeHandler。

XMLConfigBuilder的parse方法执行完毕后返回Configuration并传递给下述build重载方法,生成一个DefaultSqlSessionFactory对象,并把Configuration对象传递给其构造器。Configuration是DefaultSqlSessionFactory的一个属性。

public SqlSessionFactory build(Configuration config)
  {
    return new DefaultSqlSessionFactory(config);
  }
public class DefaultSqlSessionFactory
  implements SqlSessionFactory
{
  private final Configuration configuration;
  
  public DefaultSqlSessionFactory(Configuration configuration)
  {
    this.configuration = configuration;
  }
  //do something...
}

至此完成创建SqlSessionFactory全过程,最后将DefaultSqlSessionFactory对象返回即可。

1.1 构建Configuration

在SqlSessionFactory构建中,Configuration是最重要的,它的作用是:

  • 读入配置文件,包括基础配置的XML和映射器XML(或注解)。
  • 初始化一些基础配置,比如MyBatis的别名等,一些重要的类对象(比如插件、映射器、Object工厂、typeHandler对象等)。
  • 提供单例,为后续创建SessionFactory服务提供配置的参数。
  • 执行一些重要对象的初始化方法。

显然Configuration不会是一个很简单的类,MyBatis的配置信息都来自于此,几乎所有的配置都可以在这里找到踪影。由上述代码可知,Configuration是通过XMLConfigBuilder来构建的,首先它会读出所有XML配置的信息,然后把它们解析并保存在Configuration中。它会做如下初始化:

  • properties全局参数。
  • typeAliases别名。
  • Plugins插件。
  • objectFactory对象工厂。
  • objectWrapperFactory对象包装工厂。
  • reflectionFactory反射工厂。
  • settings环境设置。
  • environments数据库环境。
  • databaseIdProvider数据库标识。
  • typeHandlers类型转换器。
  • Mappers映射器

它们都会以类似typeHandler注册那样的方法被存放到Configuration中,以便未来将其取出。

1.2 构建映射器的内部组成

当XMLConfigBuilder解析XML时,会将每一个SQL和其配置的内容保存起来。一般而言,在MyBatis中一条SQL和它相关的配置信息是由3个部分组成的,他们分别是MappedStatement、SqlSource和BoundSql:

  • MappedStatement的作用是保存一个映射器节点(select|insert|delete|update)的内容。它是一个类,包括许多我们配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、resultMap、languageDriver等重要配置内容。它还有一个重要的属性sqlSource。MyBatis通过读取它来获得某条SQL配置的所有信息。
  • SqlSource是提供BoundSql对象的地方,它是MappedStatement的一个属性。注意,它是一个接口,而不是一个实现类。对它而言有这个重要的几个实现类:DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource。它的作用是根据上下文和参数解析生成需要的SQL。这个接口只定义了一个接口方法——getBoundSql(paramObject),使用它就可以得到一个BoundSql对象。
  • BoundSql是一个结果对象,也就是SqlSource通过对SQL和参数的联合解析得到的SQL和参数,它是建立SQL和参数的地方,它有3个常用的属性:sql、parameterObject、parameterMappings。
  1. parameterObject为参数本身,可以传递简单对象、POJO或者Map、@Param注解的参数。
  2. parameterMappings是一个List,它的每一个元素都是PatameterMapping对象。对象会描述参数,参数包括属性名称、表达式、javaType、jdbcType、typeHandler等重要信息。通过它就可以实现参数和SQL的结合,以便PreparedStatement能够通过它找到parameterObject对象的属性设置参数,使得程序能准确运行。
  3. sql属性就是书写在映射器里面的一条被SqlSource解析后的SQL。


2 SqlSession运行过程

有了SqlSessionFactory对象就可以轻易拿到SqlSession。而SqlSession也是一个接口,使用它并不复杂。它给出了查询、插入、更新、删除的方法,在旧版的MyBatis或iBatis中还会常常使用这些接口方法,而在新版的MyBatis中建议使用Mapper。无论如何它是理解MyBatis底层运行的核心。

2.1 构建SqlSession过程

当我们拿到了SqlSessionFactory后,通过openSession方法即可获取到SqlSession,代码如下:

SqlSession sqlSession = sqlSessionFactory.openSession();

为此追踪openSession方法:

public class DefaultSqlSessionFactory
  implements SqlSessionFactory
{
  //do something...
  public SqlSession openSession()
  {
    return openSessionFromDataSource(this.configuration.getDefaultExecutorType(), null, false);
  }
  //do something...
  private SqlSession openSessionFromDataSource(ExecutorType execType, 
  TransactionIsolationLevel level, boolean autoCommit)
  {
    Transaction tx = null;
    try
    {
      Environment environment = this.configuration.getEnvironment();
      TransactionFactory transactionFactory = 
  getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, 
  autoCommit);
      Executor executor = this.configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(this.configuration, executor, autoCommit);
    }
    catch (Exception e)
    {
      closeTransaction(tx);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    }
    finally
    {
      ErrorContext.instance().reset();
    }
  }
  //do something...
}

可以看到,上述方法创建了事务、Executor,最后调用了DefaultSqlSession的构造器创建了SqlSession。

2.2 映射器(Mapper)的动态代理

在之前的文章中,我们是这样获取映射器的:

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);

在SqlSession的实现类DefaultSqlSession中查看getMapper方法:

public class DefaultSqlSession
  implements SqlSession
{
  private final Configuration configuration;
  //do something... 
  public <T> T getMapper(Class<T> type)
  {
    return (T)this.configuration.getMapper(type, this);
  }
  //do something... 
}

显然它运用到了Configuration对象的getMapper方法,来获取对应的接口对象,继续追踪:

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

它又运用了映射器的注册器MapperRegistry来获取对应的接口对象:

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

首先它判断是否注册一个Mapper,如果没有则会抛出异常信息,如果有,就会启用MapperProxyFactory工厂来生成一个代理实例,为此再追踪newInstance方法:

public class MapperProxyFactory<T>
{
  //do something... 
  protected T newInstance(MapperProxy<T> mapperProxy)
  {
    return (T)Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[] { this.mapperInterface }, mapperProxy);
  }
  
  public T newInstance(SqlSession sqlSession)
  {
    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return (T)newInstance(mapperProxy);
  }
}

Mapper映射是通过动态代理来实现的。这里可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象,而代理的方法则被放到了MapperProxy类中:

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

可以看到这里的invoke方法逻辑。如果Mapper是一个JDK动态代理对象,那么它就会运行到invoke方法里面。invoke方法首先判断是否是一个类,然后isDefaultMethod方法判断是否是一个接口。这里Mapper是一个接口不是类,所以跳到invokeDefaultMethod方法里面(需要注意的是,笔者选用的是mybatis-3.4.6,所以会走invokeDefaultMethod方法。如果是其他版本,例如mybatis-3.4.1,则会走下述的execute方法。execute方法不再进行介绍,感兴趣可自行查看):

 @UsesJava7
  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
    throws Throwable
  {
    Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(new Class[] { Class.class, Integer.TYPE });
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    Class<?> declaringClass = method.getDeclaringClass();
    return 
      ((MethodHandles.Lookup)constructor.newInstance(new Object[] { declaringClass, 
      Integer.valueOf(15) }))
      
      .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

这里用到了java7中的方法句柄来调用方法,它是对Java中方法、构造方法和域的一个强类型的可执行的引用。类似于反射,但不用像反射一样写很多冗余代码(方法句柄笔者没有过多研究过,等以后再来补充)。

至此,相信大家已经知道MyBatis为什么只用Mapper接口便能够运行了。因为Mapper的XML文件的命名空间对应的是这个接口的全限定名,将其和代理对象绑定起来。通过动态代理技术,让这个接口运行起来,最后通过方法句柄来调用对应的SQL。只是有了这层封装,就可以采用接口编程,这样的编程更为简单明了。

2.3 SqlSession下的四大对象

实际上SqlSession的执行过程是通过Executor、StatementHandler、ParameterHandler和ResultSetHandler来完成数据库操作和结果返回的,在这里我们把它们简称为四大对象。

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

2.3.1 Executor——执行器

Executor是一个执行器。SqlSession其实是一个门面,真正干活的是执行器,它是一个真正执行Java和数据库交互的对象,所以它十分的重要。MyBatis中由3种执行器。我们可以在MyBatis的配置文件中进行选择,具体可以查看笔者之前写过的文章《MyBatis之配置》

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

执行器提供了查询(query)方法、更新(update)方法和相关的事务方法,这些和其他框架并无不同。先来看看MyBatis是如何创建Executor的,这段代码在Configuration类当中:

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

其实上面讲构建SqlSession过程中已经看到调用该方法了。MyBatis将根据配置类型来确定需要创建哪一种Executor,它的缓存则用CachingExecutor进行包装Executor。在创建完对象之后,由上所述,会去执行这样的一行代码:

Executor executor = (Executor)this.interceptorChain.pluginAll(executor);

这就是MyBatis的插件。它将构建一层层的动态代理对象,我们可以修改在调度真实的Executor方法之前执行配置插件的代码,这就是插件的原理。

现在再来看看其方法内部,以SIMPLE执行器SimpleExecutor的query方法作为例子进行讲解,代码如下:

public class SimpleExecutor
  extends BaseExecutor
{
  //do something...
  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(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    }
    finally
    {
      closeStatement(stmt);
    }
  }
  //do something...
  private Statement prepareStatement(StatementHandler handler, Log statementLog)
    throws SQLException
  {
    Connection connection = getConnection(statementLog);
    Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
}

显然MyBatis根据Configuration来构建StatementHandler,然后使用prepareStatement方法,对SQL编译和参数进行初始化。该方法首先调用了StatementHandler的prepare()进行了预编译和基础的设置,然后通过StatementHandler的parameterize()来设置参数,最后使用StatementHandler的query方法,把ResultHandler传递进去,使用它组织结果返回给调用者来完成一次查询。

2.3.2 StatementHandler——数据库会话器

顾名思义,数据库会话器就是专门处理数据库会话的。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)this.interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

很显然创建的真实对象是一个RoutingStatementHandler的对象,它实现了接口StatementHandler。和Executor一样,用代理对象做一层层封装。

RoutingStatementHandler不是真实的服务对象,它是通过适配模式来找到对应的StatementHandler来执行的。在MyBatis种,与Executor一样,RoutingStatementHandler分为3种:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。它所对应的是JDBC的Statement、PreparedStatement(预编译处理)和CallableStatement(存储过程处理)。

在初始化RoutingStatementHandler对象时,它会根据上下文环境决定创建哪个具体的StatementHandler对象实例,代码如下:

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: 
      this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case PREPARED: 
      this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case CALLABLE: 
      this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    default: 
      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }
  //do something...
}

它定义了一个对象的适配器——delegate,它是一个StatementHandler接口对象,然后构造方法根据配置来适配对应的StatementHandler对象。它的作用是给3个接口对象的使用提供一个统一且简易的适配器。此为对象的适配,可以使用对象适配器来尽可能地使用已有的类对外提供服务,可以根据需要对外屏蔽或者提供服务,甚至是加入新的服务。有些读者可能已经发觉了,这里是很典型的工厂模式,具体来说是简单工厂。工厂模式的介绍和使用可以参考笔者的另一篇文章《设计模式之工厂模式》

以最常用的PreparedStatementHandler为例,看看MyBatis是怎么执行查询的。Executor执行查询时会执行StatementHandler的prepare、parameterize和query方法,其中PreparedStatementHandler的prepare方法代码如下:

public abstract class BaseStatementHandler
  implements StatementHandler
{
  //do something...
  public Statement prepare(Connection connection, Integer transactionTimeout)
    throws SQLException
  {
    ErrorContext.instance().sql(this.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);
    }
  }
  //do something...
}

instantiateStatement()方法是对SQL进行了预编译,然后做一些基础配置,比如超时、获取的最大行数等的设置。Executor中会调用它的parameterize()方法来设置参数,代码如下:

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

显然这个时候它是调用ParameterHandler来完成的,下节再来讨论它是如何实现的,这里先看查询的方法——执行SQL返回结果,代码如下:

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

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

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

2.3.3 ParameterHandler——参数处理器

MyBatis是通过ParameterHandler对预编译语句进行参数设置的,它的作用是完成对预编译参数的设置,它的接口定义如下:

public abstract interface ParameterHandler
{
  public abstract Object getParameterObject();
  
  public abstract void setParameters(PreparedStatement paramPreparedStatement)
    throws SQLException;
}
  • getParameterObject()方法的作用是返回参数对象。
  • setParameters()方法的作用是设置预编译SQL语句的参数。

MyBatis为ParameterHandler提供了一个实现类DefaultParameterHandler,setParameters的实现,代码如下:

public void setParameters(PreparedStatement ps)
  {
    ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++)
      {
        ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT)
        {
          String propertyName = parameterMapping.getProperty();
          Object value;
          Object value;
          if (this.boundSql.hasAdditionalParameter(propertyName))
          {
            value = this.boundSql.getAdditionalParameter(propertyName);
          }
          else
          {
            Object value;
            if (this.parameterObject == null)
            {
              value = null;
            }
            else
            {
              Object value;
              if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass()))
              {
                value = this.parameterObject;
              }
              else
              {
                MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                value = metaObject.getValue(propertyName);
              }
            }
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if ((value == null) && (jdbcType == null)) {
            jdbcType = this.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转换参数,如果有设置,那么它会根据签名注册的typeHandler对参数进行处理。而typeHandler也是在MyBatis初始化时,注册在Configuration里面的,需要时就可以直接拿来用了。没有则生成MetaObject,MetaObject是MyBatis的工具类,它可以有效读取或者修改一些重要对象的属性。通过getValue方法来获取当前执行的SQL及其参数。MyBatis就是通过这样完成参数设置的。

2.3.4 ResultSetHandler——结果处理器

ResultSetHandler是组装结果集返回的,ResultSetHandler的接口定义如下:


public abstract interface ResultSetHandler
{
  public abstract <E> List<E> handleResultSets(Statement paramStatement)
    throws SQLException;
  
  public abstract <E> Cursor<E> handleCursorResultSets(Statement paramStatement)
    throws SQLException;
  
  public abstract void handleOutputParameters(CallableStatement paramCallableStatement)
    throws SQLException;
}

其中,handleResultSets()方法,它是包装结果集的;handleCursorResultSets()方法是处理游标的;handleOutputParameters()方法是处理存储过程输出参数的。

2.4 SqlSession运行总结

SqlSession的运行原理十分重要,它是插件的基础,这里以一次查询或者更新进行总结,以加深对MyBatis内部运行的印象,SqlSession内部运行如下图所示:

SqlSession是通过使用执行器Executor调度StatementHandler来运行的。而StatementHandler经过3步:

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

其中,parameterize是调用parameterHandler的方法设置的,而参数是根据类型处理器typeHandler处理的。query/update方法通过ResultSetHandler进行处理结果的封装,如果是update语句,就返回整数,否则就通过typeHandler处理结果类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。这便是SqlSession执行的过程。

参考资料:[1]杨开振 周吉文 梁华辉 谭茂华.Java EE 互联网轻量级框架整合开发:SSM框架(Spring MVC+Spring+MyBatis)和Redis实现[M].北京:电子工业出版社,2017.7

猜你喜欢

转载自blog.csdn.net/weixin_30342639/article/details/81292074