Spring全家桶Java基础

MyBatis详解6.MyBatis技术内幕

2019-01-24  本文已影响0人  卢卡斯哔哔哔

点击进入我的博客

MyBatis详解1.概述
MyBatis详解2.MyBatis使用入门
MyBatis详解3.MyBatis配置详解
MyBatis详解4.映射器Mapper
MyBatis详解5.动态SQL
MyBatis详解6.MyBatis技术内幕
MyBatis详解7.插件
MyBatis详解8.集成Spring

1 构建SqlSessionFactory的过程

通过SqlSessionFactoryBuilder构建SqlSessionFactory共有两步:

  1. 通过XMLConfigBuilder解析MyBatis的配置文件,并读取到Confinguration对象中。
  2. 使用Confinguration对象去创建SqlSessionFactory,由于SqlSessionFactory是一个接口,最终构建出的其实是DefaultSqlSessionFactory的对象。
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 通过XMLConfigBuilder读入配置
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      // ......
    }
  }
    
  public SqlSessionFactory build(Configuration config) {
    // 返回的是DefaultSqlSessionFactory
    return new DefaultSqlSessionFactory(config);
  }
Configuration的主要作用如下:

2 映射器的内部构成

一个映射器是由3个部分组成
  1. MappedStatement,它保存映射器的一个节点(select、insert、delete、update)。包括许多我们配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、languageDriver等重要配置内容。
  2. SqlSource,是一个接口,它的主要作用是根据参数和其他的规则组装SQL,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
  3. BoundSql,它是建立SQL和参数的地方。它有3个常用的属性:SQL、parameterObject、parameterMappings。
映射器的内部构成
BoundSql的主要属性
parameterObject的细节

3 SqlSession的运行过程

3.1 Mapper的动态代理

我们自定义的Mapper接口想要发挥功能,必须有具体的实现类,在MyBatis中是通过为Mapper每个接口提供一个动态代理类来实现的。整个过程主要有三个类:MapperProxyFactory、MapperProxy、MapperMethod。

public class MapperProxyFactory<T> {
   
  // 这里可以看到是通过Java的动态代理来实现的,具体代理的方法被放到来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);
  }
}
// 实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
  
  // 对代理类的所有方法的执行,都会进入到invoke方法中
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 此处判断是否是Object类的方法,如toString()、clone(),如果是则直接执行不进行代理
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    // 如果不是Object类的方法,则初始化一个MapperMethod并放入缓存中
    // 或者从缓存中取出之前的MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 调用MapperMethod执行对应
    return mapperMethod.execute(sqlSession, args);
  }
}
public class MapperMethod {
  // MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中
  // 实际上它最后就是通过SqlSession对象去运行对象的SQL。
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: { //...
      }
      case UPDATE: { //...
      }
      case DELETE: { //...
      }
      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:
        //...
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }

    return result;
  }  
}

3.2 SqlSession中的对象

Mapper执行的过程是通过Executor、StatementHandler、ParameterHandler和ResultHandler来完成数据库操作和结果返回的:

3.3 执行器Executor

执行器是一个真正执行Java和数据库交互的类,一共有三种执行器,我们可以在MyBatis的配置文件中设置defaultExecutorType属性进行选择。

// Configure类中创建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) {
      executor = new CachingExecutor(executor);
    }
    // 在executor完成创建之后,会通过interceptorChain来添加插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

创建Executor的具体逻辑在Configure类中,可以看到,在Executor创建完成之后,会通过interceptorChain来添加插件,通过代理到方式,在调度真实的Executor方法之前执行插件代码来完成功能。

Executor的具体执行逻辑

我们通过SimpleExecutor来看一下Executor的具体执行逻辑:

  1. 根据Configuration来构建StatementHandler
  2. 然后使用prepareStatement方法,对SQL编译并对参数进行初始化
  3. 在prepareStatement方法中,调用了StatementHandler的prepared进行了预编译和基础设置,然后通过StatementHandler的parameterize来设置参数并执行。
  4. 包装好的Statement通过StatementHandler来执行,并把结果传递给resultHandler。
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();
      // (1)根据 Configuration来构建Statementhandler
      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, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
}

3.4 数据库会话器StatementHandler

StatementHandler就是专门处理数据库会话的,创建StatementHandler的过程在Configuration中。

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

很显然创建的真实对象是一个RoutingStatementHandler对象,它实现了接口StatementHandler。从RoutingStatementHandler的构造方法来看,它其实是使用来委派模式来把具体的StatementHandler类型隐藏起来,通过RoutingStatementHandler来统一管理。一共用三种具体的StatementHandler类型:SimpleHandler、PreparedStatementHandler、CallableStatementHandler。

通过StatementHandler看执行细节

在Executor的具体执行逻辑中,我们主要关注StatementHandler的prepared、parameterize两个方法。

public abstract class BaseStatementHandler implements StatementHandler {
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // instantiateStatement对SQL进行了预编译
      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);
    }
  }
}
public class PreparedStatementHandler extends BaseStatementHandler {
  // 调用parameterize去设置参数,可以发现是通过parameterHandler来具体执行的
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
}
public class PreparedStatementHandler extends BaseStatementHandler {
  // 具体的查询就是通过PreparedStatement#execute来执行的
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
}

3.5 参数处理器ParameterHandler

MyBatis是通过ParameterHandler对预编译的语句进行参数设置的。

public interface ParameterHandler {
  // 返回参数对象
  Object getParameterObject();
  // 设置预编译的SQL语句的参数
  void setParameters(PreparedStatement ps) throws SQLException;
}
public class DefaultParameterHandler implements ParameterHandler {

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

MyBatis为ParameterHandler提供了一个实现类DefaultParameterHandler,具体执行过程还是从 parameterObject对象中取参数然后使用typeHandler进行参数处理,而typeHandler也是在My Batis初始化的时候,注册在Configuration里面的,我们需要的时候可以直接拿来用。

3.6 ResultSetHandler

public interface ResultSetHandler {
  // 包装结果集的
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  // 处理存储过程输出参数的
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
  void handleOutputParameters(CallableStatement cs) throws SQLException;
}

MyBatis为我们提供了一个DefaultResultSetHandler类,在默认的情况下都是通过这个类进行处理的。这个类JAVASSIST或者CGLIB作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回。

3.7 总结

image.png
上一篇下一篇

猜你喜欢

热点阅读