深入浅出Mybatis源码解析——SqlSession执行主流程(补)

前言

由于Mybatis的源码系列文章,期间有长时间的脱节,导致笔者在写代码解析的时候中间出现了断裂,这个要和大家说声对不起,因此本篇文章是补深入浅出Mybatis源码解析——获取Mapper代理对象流程照片文章的,在这篇文章中简单的说了一下SqlSession执行主流程,也只是简单的说了说,后面的核心代码还没有涉及。

说到这里,我们还是尽快进入主题,首先回顾下前面提到的那篇文章最后的代码,如下:

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
	try {
		// 根据传入的statementId,获取MappedStatement对象
		MappedStatement ms = configuration.getMappedStatement(statement);
		// 调用执行器的查询方法
		// RowBounds是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)
		// wrapCollection(parameter)是用来装饰集合或者数组参数
		return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
	} catch (Exception e) {
		throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
	} finally {
		ErrorContext.instance().reset();
	}
}

一、querying database

在前面说到的那篇文章中,值简单的解析了getMappedStatement和wrapCollection方法,那么这里将会正式进入query方法,代码如下:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

在上面的代码中,我们也看到了前一篇文章解析的getBoundSql,关于中间的createCacheKey部分,感兴趣的同学可以自己去看看具体的实现,这里就先不说了,我们还是继续跟query这个方法,看看它里面的具体实现,代码如下:

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 从一级缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    	// 如果一级缓存没有数据,则从数据库查询数据
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

上面的代码中主要的两个方法是从一级缓存中获取数据和从数据库查询数据,这里我们假设没有缓存,来看看查询数据库的方法的代码。如下:

  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 {
      // 执行查询
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

从上面的代码中,我们看到了关于缓存的设置,这个我们就不看了,还是继续看doQuery方法,如下:

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
		BoundSql boundSql) throws SQLException {
	Statement stmt = null;
	try {
		// 获取Configuration对象
		Configuration configuration = ms.getConfiguration();
		// 创建RoutingStatementHandler,用来处理Statement
		// RoutingStatementHandler类中初始化delegate类(SimpleStatementHandler、PreparedStatementHandler)
		StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
				resultHandler, boundSql);
		// 子流程1:设置参数
		stmt = prepareStatement(handler, ms.getStatementLog());
		// 子流程2:执行SQL语句(已经设置过参数),并且映射结果集
		return handler.query(stmt, resultHandler);
	} finally {
		closeStatement(stmt);
	}
}

 在前面的代码中,我们看到这短短的代码中步骤还是比较多的,首先是获取Configuration对象,然后创建RoutingStatementHandler,用来处理Statement,之后再在RoutingStatementHandler类中初始化delegate类(SimpleStatementHandler、PreparedStatementHandler),最后设置参数、执行SQL语句并且映射结果集。那我们先说说创建RoutingStatementHandler。因为获取Configuration对象只是把一开始初始化的configuration从MappedStatement中获取一下。

二、创建RoutingStatementHandle

这里我们来看看是怎么获创建RoutingStatementHandle的,代码如下:

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
		Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	// 创建路由功能的StatementHandler,根据MappedStatement中的StatementType
	StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
			rowBounds, resultHandler, boundSql);
	statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
	return statementHandler;
}

从上面的代码可以知道步骤大概如下:

  • 创建路由功能的StatementHandler
  • 通过statementHandler到interceptorChain去pluginAll

这里我们可以看看RoutingStatementHandler的构造器代码,如下:

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

  }

在上面的代码中,可以看到几个很熟悉的类:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler,当然这里还是要根据StatementType去判断的。

看完了这个构造器我们要继续看子流程1:设置参数这个步骤了。

三、参数设置

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
	Statement stmt;
	// 获取连接
	Connection connection = getConnection(statementLog);
	// 创建Statement(PreparedStatement、Statement、CallableStatement)
	stmt = handler.prepare(connection, transaction.getTimeout());
	// SQL参数设置
	handler.parameterize(stmt);
	return stmt;
}

上面的代码大概分为三步:

  1. 获取连接
  2. 创建Statement(PreparedStatement、Statement、CallableStatement)
  3. SQL参数设置

那就先看看获取连接:

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

这里首先通过transaction对象来获取connection,然后statementLog参数是否为true,如果是则通过映射创建实例。最后返回connection对象。

那继续看看创建Statement:

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 实例化Statement,比如PreparedStatement
      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);
    }
  }

这段代码的逻辑很简单,就不多说了,我们继续SQL参数设置代码的解析:

PreparedStatementHandler.java

  @Override
  public void parameterize(Statement statement) throws SQLException {
	// 通过ParameterHandler处理参数
    parameterHandler.setParameters((PreparedStatement) statement);
  }

 

@SuppressWarnings("unchecked")
@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 jdbcType = parameterMapping.getJdbcType();
				if (value == null && jdbcType == null) {
					jdbcType = configuration.getJdbcTypeForNull();
				}
				try {
					// 给PreparedStatement设置参数
					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);
					}
				}
			}
		}
	}

}

 上面的两段代码中最核心的部分是在第二部分中,其主要步骤为:

  1. 获取要设置的参数映射信息
  2. 处理入参
  3. 获取属性名称(然后再通过这个属性名称进行其他处理)
  4. 获取每个参数的类型处理器,去设置入参和获取返回值
  5. 获取每个参数的JdbcType
  6. 给PreparedStatement设置参数

四、执行SQL语句

在说完了参数设置后,最后我们来看看执行SQL语句的逻辑:

PreparedStatementHandler.java

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行PreparedStatement,也就是执行SQL语句
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

可以看到上面的代码中有两个主要步骤:执行SQL语句、处理结果集。这里我们只说执行SQL语句的,关于处理结果集流给我们的下一篇文章吧,也就是最后一篇文章。

其实这里的执行SQL就是设计到数据库的底层执行代码了,这里我就只以MySQL的来展示一段代码,由于笔者还没设计这一块,所以同学们感兴趣的,可以自己去看看。

    @Override
    public boolean execute() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            boolean returnVal = false;

            checkStreamability();

            setInOutParamsOnServer();
            setOutParams();

            returnVal = super.execute();

            if (this.callingStoredFunction) {
                this.functionReturnValueResults = this.results;
                this.functionReturnValueResults.next();
                this.results = null;
            }

            retrieveOutParams();

            if (!this.callingStoredFunction) {
                return returnVal;
            }

            // Functions can't return results
            return false;
        }
    }

 

由于篇幅原因,本篇文章就先到这里了,因为笔者的水平,也没有做太深的说明,只供一些入门的同学来相互学习,下一篇底阿妈将是本系列的最后一篇文章了,后面笔者将写点其他系列的文章,譬如MySQL,然后将会继续回到Java的领域内,进行深入的学习,这也是笔者2020年的flag。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值