Mybatis的运行流程浅析

发布时间:2022-08-19 14:10

     在讲述原理之前,我们需要了解一些基础的知识,Java的代理技术,如果不理解为什么的话,我们想一下,为什么mapper仅仅是一个姐恶口,而不是一个包含逻辑的实现类,一个借口是没有办法去执行的,那么是怎么执行的呢,所以有必要理解一下动态代理。

 构建SqlSessionFactory的过程:

     SqlSessionFactory是mybatis的核心类之一,起追重要的功能就是提供mybatis的核心接口SqlSession,所以,我么和你首先要创建SqlSessionFactory为我们提供配置文件和相关的参数,其构造的方式为Builder够造方式,可以通过SqlSessionFactoryBuilder去构建,分为两步:
第一步:通过ConfigBuilder类解析配置文件,读出参数的配置,并将读取的数据存入到Configuration类中,这个类包含了几乎所有的mybatis的相关的配置。
第二步:使用Configuration对象创建SqlSessionFactory。Mybatis中的SqlSessionFactory是一个接口,这里采用的是抽象工厂,为此,我们需要找到其实现类,一般的实现类为默认的DefaultSqlSessionFactory类,大部分情况下没有必要自己去创建一个新的SqlSessionFactory的实现类。Builder构造模式对于复杂的对象而言直接构造是困难的,而且写出来的代码很不容易维护,而且弹性比较差,对此我们可以使用builder构建模式一步步有秩序的构建,例如全局的配置参数等,交给了Configuration类,DefaultSqlSessionFactory类就可以构建一个复杂的SqlSessionFactory类。

1.构建COnfiguration

configuration类的作用是很重要的,它的作用有以下几个方面:
1.基础的为读入配置的xml文件和mapper的映射的xml文件。
2.初始化基础的配置,比如mybatis的别名,类型转换等,一些重要的对象,插件,映射器等等。创建为单例,为后续创建SqlSessionFactory提供了配置的参数。
3.执行一些重要的对象方法,初始化配置信息。一般其初始化的内容有:
properties全局参数;settings设置;typeAliases别名;type Handler类型处理器;ObjectFactory对象;plugin插件;environment环境;DataBaseProvider 数据库标识;Mapper映射器;


2.剖析映射器内部组成

 一般的,一个映射器有三部分组成:
1.MappedStatement,它保存映射器的一个节点,(如selectinsert|delete|update)包括许多我饿们配置的SQL和sql的id,缓存信息、resultmap、paramterType,resultType等的重要的配置内容。
2.SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
3.BoundSql,它是建立SQL和参数的地方。有三个常用的属性,SQL,parameterObject、parameterMappings。
MappedStatement对象涉及的东西较多,一般不去修改它,因为容易产生不必要的错误,S起来Source是一个接口,他的主要作用是根据参数和其它的规则组装拼接Sql,包括动态的sql语句, 这些都很复杂。对于参数和SQL而言,主要的规则都反映在BoundSql类对象上,在插件中往往需要拿到它进而可以拿到它运行当前的sql和参数以及参数的规则,作出适当的修改。
对于BoundSql,其有三个主要的属性,parameterMappings,parameterObject和sql。
其中parameterObject为参数本身,可以通过传递对象,pojo,map或者@Param注解的参数。
(1)传递简单对象:比如当我们传递int类型时,Mybatis会把参数变为Integer对象进行传递,类似的如long,float等也是如此。
(2)如果传递的是pojo或者是map,那么这个parameterObject旧手机我们传入的pojo或者是map不变。
(3)当传递多个参数如果没有@Param注解,那么mybatis就会把parameterObject变为一个Map对象,其键值的关系是按照吮吸来规划的,所以在编写的时候我们可以使用#{param1}或者#{1}进行引用第一个参数。
(4)如果使用了@Param注解,那么mybatis也会把parameterObject转换为一个Map对象,只是将数字键值改会为注解的值。
parameterMappings是一个List,每一个元素都是ParameterMapping的对象,这个对象会描述我们的参数,参数包括属性,名称,表达式,javaType,jdbcType等重要的信息,我们一般不需要去修改这里,其可以实现参数和SQL的结合,以便于PreparedStatement能够通过它找到parameterObject对象的属性并设置参数,确保程序能够正常运行。
sql 属性就是我们书写在映射器里面的一条sql语句,大多数时候我许修改这个属性,只有在使用插件的情况下,我们可以进行改写。这个一定要谨慎,如果操作不当,会使得整个框架瘫痪。

3.构建SqlSessionFactory:

当我们有了Configuration对象之后,就可以较为轻松的构建SqlSessionFactory了,我们可以通过下面的方式来构建:
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这样的化,mybatis会根据Configuration类中的配置的信息构建一个SqlSessionFactory对象。

4.SqlSession运行过程:

SqlSession是一个接口,它的使用并不复杂,构建的SqlSessionFactory可以轻易的拿到SqlSession,SqlSession给出了查询,插入,更新和删除的方法,在旧版的iBatis中,常常使用这些接口,而在新版的Mybatis 中建议我们使用Mapper。

(1)映射的动态代理:

其是通过JDK的动态代理来实现的(代码在org.appache.ibatis.binding包中):
public class MapperProxyFactory {

  private final Class mapperInterface;
  private final Map methodCache = new ConcurrentHashMap();

  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class getMapperInterface() {
    return mapperInterface;
  }

  public Map getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

}
这里看到动态代理对接口的绑定,将代理的方法放置到代理类MapperProxy类中:
public class MapperProxy implements InvocationHandler, Serializable {

 ````````
  }

  @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);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    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方法,一旦mapper是一个代理对象,那么它就会运行到invoke方法里面,首先会判断是不是一个类,这里显然Mapper是一个接口而不是一个类,所以判定失败,就会生成MapperMethod对象,他是通过cachedMapperMethod方法对其进行初始化的,然后执行execute方法,把sqlSession和当前的参数传递进去。让我们看一下这个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;
  }
可以看到,通过获取commandType类型的命令的类型,结合switch选择来执行对应的处理,我们通过其中的一个例子来分析,我们分析一下返回多条记录的过程,也就是select情况下的executeForMany方法执行的过程:
  private  Object executeForMany(SqlSession sqlSession, Object[] args) {
    List result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.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;
  }
可以看到,传入的参数为SqlSession的类型,看到实现之后可以理解,实际上最后通过sqlSession对象去运行对象的SQL,因为映射器的xml文件的命名空间对应的就是这个接口的路径,那么根据全路径和方法的名称便可以绑定起来,通过动态代理的技术,然这个接口运行起来,之后使用命令模式,使得SqSession能够执行对应的方法,有了这层封装,我们仅需要提供一个接口,不必再去实现这个接口了。

(2)SqlSession下的四个重要的对象:

其实Mapper的执行过程是通过Executor、StatementHandler、ParammeterHandler和ResultHandler等来执行对应的sql。StatementHandler的作用是使用数据库的Statement(PreparedStatementHandler)执行操作,他起到了一个承上启下的作用,是四大对象的核心。而ParameterHandler用于SQL对参数的处理,ResultHandler是进行最后数据集的封装返回处理。

1.执行器

其是一个真正和数据库进行交互的部件,在mybatis的三种执行器中,我们可以在mybatis的配置文件中进行选择,可以通过settings元素的属性进行配置,配置defaultExecutorType来完成。
SIMPLE,简易执行器。REUSE,是一种执行器重用预处理语句。BATCH执行器重用语句和批量更新,针对批量转用的执行器。
首先通过源码看一下Mybatis是如何生成的:
在Configuration类中:
 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构建一层层的动态代理对象,在调度真实的WExecutor之前,执行配置插件的代码可以被修改。下面以SIMPLE执行器SimpleExecutor的查询方法作为例子进行分析:
public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  public  List 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.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected  Cursor doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.queryCursor(stmt);
  }

  @Override
  public List doFlushStatements(boolean isRollback) throws SQLException {
    return Collections.emptyList();
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

}
    显然可以看出,Mybatis根据Configuration来构建StatementHandler,然后使用prepareStatement方法,对SQL编译并对参数进行初始化,我们看它实现的过程,它调用了StaementHandler的prepare()进行了预编译和基础设置,然后通过StatementHandler的额parammeterize()方法来设置参数并执行,resultHandler在组装查询结果返回给调用者来完成一次查询。其中发挥主要作用的时StatementHandler。


2.数据库会话器

看一下数据库会话器是如何再Configuraiton类里面生成的:

  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;
  }
   可以看到StatementHandler接口的实现类为一个RoutingStatementHandler对象,它实现了接口StatementHandler。和Executor一样,用代理对象进行一层层的封装。RoutingStatementHandler不是真正的服务对象,看源码会发现,其实通过适配器模式进行调度的,其对应Executor‘的三种模式,分别有SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler三种。看一下源码:
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());
    }

  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    delegate.batch(statement);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    return delegate.update(statement);
  }

  @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

  @Override
  public  Cursor queryCursor(Statement statement) throws SQLException {
    return delegate.queryCursor(statement);
  }

  @Override
  public BoundSql getBoundSql() {
    return delegate.getBoundSql();
  }

  @Override
  public ParameterHandler getParameterHandler() {
    return delegate.getParameterHandler();
  }
}
    可以看到,对象的适配器对象为delegate,其是一个StatementHandler的接口对象,构造方法根据配置来适配对应的StatementHanler对象,它的作用是给实现类对象的使用提供一个统一的,简易使用的适配器,此为对象的适配器模式。之后看一下是怎么执行查询的?
     我们以PreparedStatementHandler为例,看一下prepare,parameterize和query方法是如何调度的。
prepare方法:
 @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);
    }
  }
除了一些基本的配置,如设置超时,获取最大行数设置外,instancetiateStatement()方法是对SQL进行 预编译,然后调用了prepareStatementHandler类里面的上述的预编译方法,其中其调用了java.sql中的prepareStatement方法:
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      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);
    }
  }

然后执行器Executor就会调用parameterize()方法器设置参数,它的代码如下:

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
这个时候,它调用的ParameterHandler去完成的,那么看一下其是怎么完成查询的,不过在这里先了解一下StatementHandler中的查询方法:
  @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler. handleResultSets(ps);
  }

在执行参数和SQL都被prepare()方法预编译,参数在parameterize()方法上已经进行了设置,所以到这里已经很简单了,我们只要执行SQL,然后返回结果就可以了,执行之后我们看到了ResultSetHandler对结果进行封装和返回。
所以,我们再来梳理一遍这个过程哈:
首先,Executor会调用StatementHandler的prepare()方法预编译sql语句,同时设置一些基本的运行参数,然后用prepareize()方法启动ParameterHandler设置参数,完成预编译,跟着就是执行查询,而update()也是这样的,如果需要查询,我们就会用ResultSetHandler封装返回结果给调用者。

3.参数处理器

首先看一下参数处理器声明的接口,其功能就是完成对预编译参数的设置:

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;

}
其中,第一个方法的作用是返回参数对象,setParameters()方法的作用是设置预编译SQL语句的参数,其实现类为一个默认的DefaultParameterHandler,看一下第二个方法的实现:
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List 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进行参数处理,如果有设置在配置文件中,那么就会根据签名住v额的typeHandler对参数进行预处理,而其也是在mybatis初始化的时候注册在Configuration类中的,当我们需要的的时候就可以完成参数的设置了。

4.结果处理器

     根据上面的分析可以了解到,它是用来组装结果集进行返回的,我们再来看看结果处理器(ResultSetHandler)的接口的定义,代码清单如下:

public interface ResultSetHandler {

   List handleResultSets(Statement stmt) throws SQLException;

   Cursor handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}
    其中,handleOutputParameters()方法是处理存储过程输出参数的,我们暂时不需要管他,重点看一下handleResultSet方法,他是包装结果集的,Mybatis同样为我们提供一个DefaultResultSetHandler类,在默认的情况下都是通过这个类进行处理的,这个类较为复杂,它涉及使用JAVASSIST和CGLIB作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回,基本上改动这一部分的概率很小。
    以上就是大体的运行流程的浅析,如果想要了解具体是怎么运行,怎么设计的,还需要我们返回我们的代码中继续理解。里面一些优秀的设计模式,一些优秀的设计理念是很值得我们去学习的。





ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号