Mybatis执行流程源码解析

Mybatis执行流程源码解析

上一文中,我们将Mybatis的配置解析说清楚,在此基础之上,再来看执行流程,会相对清晰和简单一些。

常规的Mybatis执行流程总体分为3步:

  1. 获取SqlSession
  2. 通过SqlSession获取Mapper接口实例
  3. 通过Mapper接口执行对应的方法

下面我们一步一步的来分析下整个过程。

获取SqlSession

SqlSession是从SqlSessionFactory中返回的,在前一文中,我们知道,默认使用的DefaultSqlSessionFactory。从最简单的无参DefaultSqlSessionFactory#openSession()方法走起:

public SqlSession openSession() {
	//从数据源打开一个新的Session
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

跟入DefaultSqlSessionFactory#openSessionFromDataSource方法,这就是打开一个新session的逻辑:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
  	//从configuration中获取environment,环境中就包含了Datasource和TransactionFactory
    final Environment environment = configuration.getEnvironment();
    //从环境中获取事务工厂(事务工厂是用来创建事务对象的)
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    //通过事务工厂创建一个新的事务。
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //关键方法,通过configuration来构建一个新的执行器Executor。
    final Executor executor = configuration.newExecutor(tx, execType);
    //构建一个新的DefaultSqlSession对象返回,其核心属性是executor。
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } .....
}

先看下新事务是如何创建的,以示例代码配置<transactionManager type="JDBC"/>为例,在解析配置中,会创建JdbcTransactionFactory类型的事务工厂,它JdbcTransactionFactory#newTransaction方法会创建一个新的JdbcTransaction类型的事务:

public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
	//创建一个新的事务对象,里面会包含数据源。
  return new JdbcTransaction(ds, level, autoCommit);
}

接下来,看看核心对象Executor是如何构建出来的,Configuration#newExecutor方法:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  //确定执行器类型,默认使用SIMPLE。
  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 {
  	//创建一个SimpleExecutor,并传入configuration对象和前面创建的transaction对象。
    executor = new SimpleExecutor(this, transaction);
  }
  //如果打开二级缓存,默认是打开的。注意想要二级缓存生效,除了这个开关,还必须配置cache才行。
  if(cacheEnabled) {
  	//使用CachingExecutor对当前的执行器包装,典型的装饰者模式使用。
    executor = new CachingExecutor(executor);
  }
  //使用插件链对当前的执行器进行包装
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

在InterceptorChain#pluginAll方法中,依次回调插件的plugin方法,对executor进行一层层的包装:

public Object pluginAll(Object target) {
	//遍历所有插件,执行插件的plugin方法,传入要扩展的目标对象。
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  //返回执行插件plugin方法之后的对象。
  return target;
}

使用构建好的executor对象,new一个DefaultSqlSession返回,这就完成了SqlSession获取。

在Mybaits中有4大插件扩展点,Executor就是其中之一,我们可以编写插件,拦截Executor的所有方法,执行额外的逻辑。另外3个扩展点,随着执行流程的推进,会一个一个浮出水面。

通过SqlSession直接查询

为了不让Mapper接口的方式产生干扰,先把清楚执行的核心脉络,先跳过Mapper接口方式,直接使用sqlsession进行操作,比如sqlSession.selectList("com.zyy.demo.mapper.CountryMapper.selectAll");

在DefaultSqlSession中,不会有具体的执行逻辑,具体逻辑封装到Executor中,常规的门面模式使用。

DefaultSqlSession#selectList方法逻辑:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
  	//通过语句ID获取MappedStatement,这都是在初始化时解析好的。
    MappedStatement ms = configuration.getMappedStatement(statement);
    //使用执行器,来执行对应的MappedStatement
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } ...
}

通过前面的执行器构造过程,我们知道这儿调用的执行器有可能是插件包装过了的,就算没有配置插件,默认也是被CachingExecutor包装过的,插件的先抛开不说,这是要和具体的插件逻辑绑定。 而CachingExecutor的逻辑参考 Mybatis缓存源码详解 的二级缓存源码解析。( https://blog.csdn.net/gruelxsp/article/details/103769381 ),详细的介绍了二级缓存的构建过程,如何通过装饰者模式来完成整个缓存的各种功能职责划分。这儿就不再解析一遍,因此我们直接定位到最终的SimpleExecutor上来,SimpleExecutor继承至BaseExecutor,执行流程的大部分逻辑在BaseExecutor中,BaseExecutor#query方法:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	 //获取
   BoundSql boundSql = ms.getBoundSql(parameter);
   //生成缓存key
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   //执行查询
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

跟入query方法:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ...
  //有必要的话,情况本地缓存
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  ...
  	//从本地缓存获取
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
       ...//本地缓存获取到了时,其他处理
    } else {
    	//本地缓存没获取到,从数据库查询
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  ...//其他处理
  return list;
}

当然继续跟queryFromDatabase方法

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 {
  	//具体的数据库查询逻辑,这儿就会调用到SimpleExecutor上了。
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
  	//本地缓存移除占位的
    localCache.removeObject(key);
  }
  //本地缓存放入数据库查询结果
  localCache.putObject(key, list);
  ...
  return list;
}

内部的doQuery方法,利用了模板方法模式,具体实现由子类SimpleExecutor实现,进入SimpleExecutor#doQuery方法:

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的构建,也是通过configuration来构建的
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    //准备JDBC驱动的statement对象(语句对象)
    stmt = prepareStatement(handler, ms.getStatementLog());
    //调用StatementHandler的query方法,来进行具体的数据查询。
    return handler.<E>query(stmt, resultHandler);
  } finally {
  	//关闭语句。
    closeStatement(stmt);
  }
}

这儿的三个方法都很重要,我们需要一个一个拆开瞧仔细了。

Configuration#newStatementHandler方法类构建核心对象StatementHandler:

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	//new一个RoutingStatementHandler,这儿又是用了装饰者模式。
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  //使用插件链对构建的RoutingStatementHandler进行扩展。
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

看下RoutingStatementHandler的实现:

public class RoutingStatementHandler implements StatementHandler {
	
	//包装的实际StatementHandler
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	
		//在构造方法中,通过语句类型,选择不同的StatementHandler。
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
        
      //默认都是用PreparedStatementHandler,其他的几乎可以不用管。
      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());
    }

  }
  
  //其他实现的StatementHandler接口方法,均是直接调用delegate的对应方法。

可以看出RoutingStatementHandler仅仅做了一件事情,就在在构造方法中判断语句的类型,然后new一个对应的StatementHandler出来,默认使用的是PreparedStatementHandler这个,在new PreparedStatementHandler时,其内部又初始化了两个核心的对象ParameterHandler、ResultSetHandler:

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  this.configuration = mappedStatement.getConfiguration();
  this.executor = executor;
  this.mappedStatement = mappedStatement;
  this.rowBounds = rowBounds;

  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.objectFactory = configuration.getObjectFactory();

  if (boundSql == null) { // issue #435, get the key before calculating the statement
    generateKeys(parameterObject);
    boundSql = mappedStatement.getBoundSql(parameterObject);
  }

  this.boundSql = boundSql;
	
	//核心对象ParameterHandler的构建,通过configuration来构建的
  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  //核心对象ResultSetHandler的构建,也是通过configuration来构建的
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

通过configuration.newParameterHandler方法来构建核心对象ParameterHandler:

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
	//通过mappedStatement获取到具体的LanguageDriver,通过它来构建ParameterHandler
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  //使用插件链对构建的ParameterHandler进行扩展。
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

以XMLLanguageDriver(默认)为例,创建ParameterHandler就是new一个DefaultParameterHandler对象,XMLLanguageDriver#createParameterHandler:

public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}

同样,通过configuration.newResultSetHandler方法来构建核心对象ResultSetHandler:

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
    ResultHandler resultHandler, BoundSql boundSql) {
  //new一个DefaultResultSetHandler对象
  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  //使用插件链对构建的ResultSetHandler进行扩展。
  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  return resultSetHandler;
}

到此,我们拆解了第一方法,StatementHandler是如何构建出来的。同时我们也见识了Mybaits的除Executor之外的其他3个插件扩展点。不知道大家有没有发现一个特点,Mybatis的4大核心对象Executor、StatementHandler、ParameterHandler、ResultSetHandler的创建,都是通过configuration.newXXX方法来构建的。

现在来看看第二个方法:

//准备JDBC驱动的statement对象(语句对象)
stmt = prepareStatement(handler, ms.getStatementLog());

SimpleExecutor#prepareStatement方法:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  //获取连接
  Connection connection = getConnection(statementLog);
  //调用StatementHandler.prepare方法,准备Statement对象
  stmt = handler.prepare(connection, transaction.getTimeout());
  //调用StatementHandler.parameterize方法,为Statement对象设置参数
  handler.parameterize(stmt);
  return stmt;
}

在父类BaseExecutor#getConnection方法中,

protected Connection getConnection(Log statementLog) throws SQLException {
	//通过事务对象来获取数据库连接(事务对象中有Datasouce,最终连接是从Datasource中获取的)
  Connection connection = transaction.getConnection();
  //如果打开了Debug日志,会使用JDK动态代理生成连接的代理对象返回。(这个连接代理对象生成Statement语句时也会生成代理语句对象,实现答应日志功能)
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

以JdbcTransaction为例,其获取连接的逻辑如下:

public Connection getConnection() throws SQLException {
	//缓存的Connection为null,则打开一个连接
  if (connection == null) {
    openConnection();
  }
  return connection;
}

protected void openConnection() throws SQLException {
		//从dataSource中获取连接
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }

获取数据库连接这块,这儿展开解析,因为当Mybatis和Spring集成后,数据源是有Spring管理的,就会调用到spring的代码上了。

获取连接后,就可以通过连接来生成Statement对象了,stmt = handler.prepare(connection, transaction.getTimeout());,同样定位到最终的 BaseStatementHandler#prepare方法上来,这又是一个模板方法:

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ...
  Statement statement = null;
  try {
  	//调用子类来生成具体的Statement,默认使用子类是PreparedStatementHandler
    statement = instantiateStatement(connection);
    //设置Statement的属性
    setStatementTimeout(statement, transactionTimeout);
    setFetchSize(statement);
    return statement;
  } ...
}

那么来看下子类PreparedStatementHandler#instantiateStatement方法实现:

protected Statement instantiateStatement(Connection connection) throws SQLException {
  //获取sql,此时的sql是已经处理好的,可以预编译的sql。
  String sql = boundSql.getSql();
  
  //KeyGenerator相关处理
  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 {
  	//调用连接的prepareStatement方法,创建一个预编译的statement。
    return connection.prepareStatement(sql);
  }
}

PreparedStatement对象创建好了之后,就可以往里面设置参数了,调用StatementHandler.parameterize方法,为Statement对象设置参数,handler.parameterize(stmt);,同理,抛开可能的插件逻辑,直接定位到PreparedStatementHandler#parameterize方法:

public void parameterize(Statement statement) throws SQLException {
	//委托给StatementHandler内部的parameterHandler来进行参数设置。
  parameterHandler.setParameters((PreparedStatement) statement);
}

在前面我们知道parameterHandler是new的一个DefaultParameterHandler对象,因此直接进入DefaultParameterHandler#setParameters方法:

public void setParameters(PreparedStatement ps) {
  ...
  //拿到SQL语句中需要设置的参数列表
  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 = 获取对应参数值(这儿代码比较繁琐,直接省略了)
        //获取参数对应的TypeHandler
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        ...
        try {
        	//调用对应typeHandler的setParameter方法来设置参数。
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } ...
      }
    }
  }
}

可以看到ParameterHandler内部,最终使用的是typeHandler来完成设置参数值的操作的。

现在第二步准备JDBC驱动的statement对象(语句对象)完成了。

最后来第三步,完成最后的数据库查询。

 //调用StatementHandler的query方法,来进行具体的数据查询。
return handler.<E>query(stmt, resultHandler);

同理,忽略各种包装,定位到PreparedStatementHandler#query方法:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //执行语句	
  ps.execute();
  //通过StatementHandler中的resultSetHandler来处理结果集。
  return resultSetHandler.<E> handleResultSets(ps);
}

同样,在构建StatementHandler时,其内部构建的resultSetHandler是DefaultResultSetHandler,因此看看它是如何处理结果集的,DefaultResultSetHandler#handleResultSets方法:

public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ...
  final List<Object> multipleResults = new ArrayList<Object>();
	//结果集数
  int resultSetCount = 0;
  //获取第一个结果集(默认使用的是PreparedStatement,只会有一个结果集)
  ResultSetWrapper rsw = getFirstResultSet(stmt);
	//从mappedStatement获取配置的ResultMap,这儿返回的是列表,通常只会有一个,对应一个结果集
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  //循环所有结果集(
  while (rsw != null && resultMapCount > resultSetCount) {
  	//获取第一个结果集映射。
    ResultMap resultMap = resultMaps.get(resultSetCount);
    //处理当前结果集数据
    handleResultSet(rsw, resultMap, multipleResults, null);
    //获取下一个结果集(PreparedStatement只会有一个)
    rsw = getNextResultSet(stmt);
    //处理完一个结果集后,清除临时数据。
    cleanUpAfterHandlingResultSet();
    //结果数+1
    resultSetCount++;
  }

  ...
	
	//包装一下返回。对但结果集来说,返回的就是multipleResults.get(0)这个列表。
  return collapseSingleResultList(multipleResults);
}

跟入最关键的handleResultSet方法:(省略了其他分支,不代表不重要)

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
 ...
        //创建一个DefaultResultHandler,它功能很简单,就是把映射的结果对象放到一个list中去
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        //处理结果集的数据
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        //把映射的结果List取出来,放到multipleResults这个list中去(multipleResults的结构就是List<List<Object>>。
        multipleResults.add(defaultResultHandler.getResultList());
 ...
}

继续跟进handleRowValues方法(同样省略一些分支):

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  ...
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}

继续handleRowValuesForSimpleResultMap方法:

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  //创建一个DefaultResultContext上下文对象,用于保存结果映射过程中的数据和状态
  DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
  //这个方法有意思,这就是Mybatis默认分页内存分页方式,结果集是有全部数据,通过这个方法,将结果集定位到想要的数据行上。
  skipRows(rsw.getResultSet(), rowBounds);
  //循环获取结果集中每一行数据,进行映射处理。注意这儿的退出条件是,取够想要的一页数据,或者结果集中已经没有数据了。
  while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    //鉴别器映射特性处理(感兴趣可看下)
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
    // 这才是最核心的地方,结果集映射为我们想要的JAVA对象。
    Object rowValue = getRowValue(rsw, discriminatedResultMap);
    // 把映射生成出来的java对象保存到上下文中,并通过resultHandler进行处理(默认就是放到一个list中去)
    storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
  }
}

先看下比较的简单的skipRows方法,用于将结果集定位到某一行数据上:

private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
  //一种直接定位方式
  if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
    if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
      rs.absolute(rowBounds.getOffset());
    }
  //或者通过循环取方式
  } else {
    for (int i = 0; i < rowBounds.getOffset(); i++) {
      rs.next();
    }
  }
}

关键的映射处理代码DefaultResultSetHandler#getRowValue方法:

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  //构建一个空的java对象出来。可以简单的理解为使用objectFactory.create方法来实例化一个对应的java类型对象出来,实际情况比这个会复制得多,就不展开细看了。
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  	//为刚创建的新java对象构建一个MetaObject对象出来,MetaObject在这儿的主要作用是为我们的java对象设置属性值(通过反射的方式)
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    
    //先通过自动映射的方式设置一遍值
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
    }
    //然后再通过属性映射设置一遍值
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    //这儿是当没有设置任何值时,是返回一个空的对象呢(即属性都没有设置值),还是返回null
    rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
  }
  return rowValue;
}

自动映射的设置值applyAutomaticMappings方法:

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
	//自动映射,这儿为自动推断结果集映射出来,并缓存起来。具体的推断逻辑不展开
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  
  boolean foundValues = false;
  if (autoMapping.size() > 0) {
  	//遍历每一个映射属性,通过typeHandler从结果集中获取值,然后通过前面创建的metaObject设置到我们的java对象中去。
    for (UnMappedColumnAutoMapping mapping : autoMapping) {
    	//调用自动推断出来的typeHandler的getResult方法,获取结果集中对应字段的值
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        //把获取的值设置到java对象中对应的属性上去。
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}

在来看看属性映射 applyPropertyMappings 方法(省去部分分支):

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  //拿到属性映射列表
  final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  //遍历每个属性映射配置,
  for (ResultMapping propertyMapping : propertyMappings) {
    String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
   ...
        //获取结果集中对应字段的值,可简单的理解为typeHandler.getResult(rs, column)来获取值,实际情况因为要处理内嵌的情况,比较复杂,不展开。
      Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
      //获取属性名
      final String property = propertyMapping.getProperty();
      ...
        //把获取的值设置到java对象中对应的属性上去。
        metaObject.setValue(property, value);
   ...
  }
  return foundValues;
}

到此,终于分析完了(初略的)第三步的,执行数据库查询和结果集映射处理,可以看出结果集的映射代码逻辑相比于其他功能,还是很复杂的。

到这儿,经典的selectList方法分析完成,不过可能有点绕晕了,有必要来个图总结下,加深点映像。

在这里插入图片描述

通过Mapper接口查询

通过Mapper接口方法来进行查询,是常规是用方式,可以拆解为两步来分析:

  1. 通过SqlSession获取Mapper接口实例
  2. 通过Mapper接口执行对应的方法

第一步,通过sqlSession.getMapper(SysUserMapper.class);方式来获取Mapper接口的一个代理对象,定位到DefaultSqlSession#getMapper方法:

public <T> T getMapper(Class<T> type) {
	//委托给configuration的getMapper方法
  return configuration.<T>getMapper(type, this);
}

进入到Configuration#getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	//委托给mapperRegistry的getMapper方法
  return mapperRegistry.getMapper(type, sqlSession);
}

再进入到MapperRegistry#getMapper方法,这里面就是来创建mapper接口代理对象的地方:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	//首先获取到对应mapper类型的MapperProxyFactory,这个MapperProxyFactory是在解析的过程中构建好的。
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  ...
  	//通过MapperProxyFactory来构建一个代理对象返回。
    return mapperProxyFactory.newInstance(sqlSession);
  ...
}

跟进去MapperProxyFactory#newInstance方法:

public T newInstance(SqlSession sqlSession) {
	//new一个MapperProxy对象,注意MapperProxy是实现了InvocationHandler接口的。
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
 	//调用newInstance(MapperProxy<T> mapperProxy)方法,它会使用jdk动态代理生成代理对象。
  return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
		//通过jdk的动态代理生成mapper接口的代理对象返回。
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

到这儿,第一步获取mapper接口的代理对象完成。

第二步,调用Mapper接口中定义的方法,如userMapper.selectById((long) 1);,它会调用到代理对象上去,即会调用到MapperProxy#invoke方法上去:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //进行一些额外的处理。
  ...
  //获取MapperMethod对象,这儿会有一层缓存处理
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  //通过MapperMethod的execute方法来执行具体的逻辑。
  return mapperMethod.execute(sqlSession, args);
}

简单看下cachedMapperMethod方法:

private MapperMethod cachedMapperMethod(Method method) {
	//从缓存中获取
  MapperMethod mapperMethod = methodCache.get(method);
  if (mapperMethod == null) {
  	//没有获取到就new一个出来,并放到缓存中,主要构建SqlCommand和MethodSignature这两个对象保存起来。
    mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
    methodCache.put(method, mapperMethod);
  }
  return mapperMethod;
}

主要还是来看下MapperMethod#execute方法,就是通过它将执行转移到了sqlsession上:

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
  	//insert语句,调用到sqlSession.insert方法
    case INSERT: {
   Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    //UPDATE语句,调用到sqlSession.update方法
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    //UPDATE语句,调用到sqlSession.delete方法
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    //select语句,根据方法的返回结果类型,进行不同的逻辑处理。
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
      	//里面会调用sqlSession.select方法
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
      	//里面会调用sqlSession.selectList方法
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
      	//里面会调用sqlSession.selectMap方法
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
      	//里面会调用sqlSession.selectCursor方法
        result = executeForCursor(sqlSession, args);
      } else {
      	//返回结果是常规java对象时,直接调用sqlSession.selectOne方法。
        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());
  }
  ...
  return result;
}

调用到了SqlSession的方法上之后,后续的流程就和通过SqlSession直接查询的逻辑一样了。因此通过Mapper接口这种方式大大的提高了使用的便利性,但其实现并不复杂,只是通过JDK动态代理为Mapper接口生成代理对象,代理逻辑中将方法调用转移到SqlSession对应的方法上即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值