Mybatis(五)SQL执行流程

本系列文章:
  Mybatis(一)Mybatis的基本使用
  Mybatis(二)Mybatis的高级使用
  Mybatis(三)配置文件解析流程
  Mybatis(四)映射文件解析流程
  Mybatis(五)SQL执行流程
  Mybatis(六)数据源、缓存机制、插件机制

  经过前面复杂的解析过程后,现在MyBatis 已经进入了就绪状态,等待使用者调用。
  MyBatis执行SQL的过程比较复杂,包括但不限于以下技术点:

  1. 为mapper接口生成实现类。
  2. 根据配置信息生成SQL,并将运行时参数设置到SQL中。
  3. 一二级缓存的实现。
  4. 插件机制。
  5. 数据库连接的获取与管理。
  6. 查询结果的处理,以及延迟加载等。

  此处仅分析以上列表中的第 1 个、第 2 个以及第 6 个技术点。

一、SQL执行入口

  在单独使用MyBatis进行数据库操作时,我们通常都会先调用SqlSession接口的getMapper方法为我们的Mapper接口生成实现类。然后就可以通过Mapper进行数据库操作。示例:

	ArticleMapper articleMapper = session.getMapper(ArticleMapper.class);
	Article article = articleMapper.findOne(1);

  先来看一下Mapper接口的代理对象创建过程。

1.1 为Mapper接口创建代理对象

  从DefaultSqlSession的getMapper方法开始:

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

  接着看Configuration:

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

  接着MapperRegistry:

 	public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    	//从knownMappers中获取与type对应的MapperProxyFactory
    	final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    	if (mapperProxyFactory == null) {
      		throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    	}
    	try {
      		//创建代理对象
      		return mapperProxyFactory.newInstance(sqlSession);
    	} catch (Exception e) {
      		throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    	}
  	}

  MyBatis在解析配置文件的<mappers>节点的过程中,会调用MapperRegistry的addMapper方法将Class到MapperProxyFactory对象的映射关系存入到knownMappers。
  在获取到MapperProxyFactory对象后,即可调用工厂方法为Mapper接口生成代理对象,看MapperProxyFactory:

  	public T newInstance(SqlSession sqlSession) {
    	//创建MapperProxy对象,MapperProxy实现了InvocationHandler接口,
    	//代理逻辑封装在此类中
    	final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    	return newInstance(mapperProxy);
  	}

  	protected T newInstance(MapperProxy<T> mapperProxy) {
    	//通过JDK动态代理创建代理对象
    	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  	}

  首先创建了一个MapperProxy对象,该对象实现了InvocationHandler接口。然后将对象作为参数传给重载方法,并在重载方法中调用JDK动态代理接口为Mapper生成代理对象。代理对象已经创建完毕,下面就可以调用接口方法进行数据库操作了。

1.2 执行代理逻辑

  Mapper接口方法的代理逻辑首先会对拦截的方法进行一些检测,以决定是否执行后续的数据库操作。看MapperProxy:

	public Object invoke(Object proxy,Method method, Object[] args) throws Throwable {
		try {
			//如果方法是定义在Object类中的,则直接调用
			if (Object.class.equals(method.getDeclaringClass())) {
				return method.invoke(this, args);
				/*
				 * 下面的代码最早出现在mybatis-3.4.2版本中,用于支持JDK 1.8中的
				 * 新特性 - 默认方法
				 */
			} else if (isDefaultMethod(method)) {
				return invokeDefaultMethod(proxy, method, args);
			}
		} catch (Throwable t) {
			throw ExceptionUtil.unwrapThrowable(t);
		}
		//从缓存中获取MapperMethod对象,若缓存未命中,则创建MapperMethod对象
		final MapperMethod mapperMethod = cachedMapperMethod(method);
		//调用execute方法执行SQL
		return mapperMethod.execute(sqlSession, args);
	}

  代理逻辑会首先检测被拦截的方法是不是定义在Object中的,比如equals、hashCode方法等。对于这类方法,直接执行。完成相关检测后,创建MapperMethod对象,然后通过该对象中的execute方法执行SQL。先来看一下MapperMethod对象的创建过程。

  • 1、 创建MapperMethod对象
public class MapperMethod {

  	private final SqlCommand command;
  	private final MethodSignature method;

  	public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    	//创建SqlCommand对象,该对象包含一些和SQL相关的信息
    	this.command = new SqlCommand(config, mapperInterface, method);
    	//创建MethodSignature对象,由类名可知,该对象包含了被拦截方法的一些信息
    	this.method = new MethodSignature(config, mapperInterface, method);
  	}
}

  MapperMethod构造方法的逻辑很简单,主要是创建SqlCommand和MethodSignature对象。

  • 2、 创建 SqlCommand对象
      SqlCommand(在MapperMethod类中)中保存了一些和SQL相关的信息:
public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      	final String methodName = method.getName();
      	final Class<?> declaringClass = method.getDeclaringClass();
      	//解析MappedStatement
      	MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          	configuration);
      	//检测当前方法是否有对应的MappedStatement
      	if (ms == null) {
        	//检测当前方法是否有@Flush注解
        	if (method.getAnnotation(Flush.class) != null) {
          		//设置name和type遍历
          		name = null;
          		type = SqlCommandType.FLUSH;
        	} else {
          		//若ms==null且方法无@Flush注解,此时抛出异常。
          		throw new BindingException("Invalid bound statement (not found): "
              		+ mapperInterface.getName() + "." + methodName);
        	}
      	} else {
        	//设置name和type变量
        	name = ms.getId();
        	type = ms.getSqlCommandType();
        	if (type == SqlCommandType.UNKNOWN) {
          		throw new BindingException("Unknown execution method for: " + name);
        	}
      	}
    }
}

  SqlCommand的构造方法主要用于初始化它的两个成员变量。

  • 3、 创建 MethodSignature对象
      MethodSignature(在MapperMethod类中) 即方法签名,顾名思义,该类保存了一些和目标方法相关的信息。比如目标方法的返回类型,目标方法的参数列表信息等。
public static class MethodSignature {

   	private final boolean returnsMany;
    private final boolean returnsMap;
    private final boolean returnsVoid;
    private final boolean returnsCursor;
    private final boolean returnsOptional;
    private final Class<?> returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;

    public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      	//通过反射解析方法返回类型
      	Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
     	if (resolvedReturnType instanceof Class<?>) {
        	this.returnType = (Class<?>) resolvedReturnType;
      	} else if (resolvedReturnType instanceof ParameterizedType) {
        	this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      	} else {
        	this.returnType = method.getReturnType();
      	}
      	//检测返回值类型是否是void、集合或数组、Cursor、Map等
      	this.returnsVoid = void.class.equals(this.returnType);
      	this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      	this.returnsCursor = Cursor.class.equals(this.returnType);
      	this.returnsOptional = Optional.class.equals(this.returnType);
      	//解析@MapKey注解,获取注解内容
      	this.mapKey = getMapKey(method);
      	this.returnsMap = this.mapKey != null;
      	//获取RowBounds参数在参数列表中的位置,如果参数列表中
      	//包含多个RowBounds参数,此方法会抛出异常
      	this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      	//获取ResultHandler参数在参数列表中的位置
      	this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      	//解析参数列表
      	this.paramNameResolver = new ParamNameResolver(configuration, method);
    }
}

  上面的代码用于检测目标方法的返回类型,以及解析目标方法参数列表。其中,检测返回类型的目的是为避免查询方法返回错误的类型。下面分析参数列表的解析过程,即ParamNameResolver:

public class ParamNameResolver {

  	public static final String GENERIC_NAME_PREFIX = "param";
 	private final boolean useActualParamName;
  	private final SortedMap<Integer, String> names;
  	private boolean hasParamAnnotation;

  	public ParamNameResolver(Configuration config, Method method) {
    	this.useActualParamName = config.isUseActualParamName();
    	//获取参数类型列表
    	final Class<?>[] paramTypes = method.getParameterTypes();
    	//获取参数注解
    	final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    	final SortedMap<Integer, String> map = new TreeMap<>();
    	int paramCount = paramAnnotations.length;
    	for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      		//检测当前的参数类型是否为RowBounds或ResultHandler
      		if (isSpecialParameter(paramTypes[paramIndex])) {
        		continue;
      		}
      		String name = null;
      		for (Annotation annotation : paramAnnotations[paramIndex]) {
        		if (annotation instanceof Param) {
          			hasParamAnnotation = true;
          			//获取@Param注解内容
          			name = ((Param) annotation).value();
          			break;
        		}
      		}
      		//name为空,表明未给参数配置@Param注解
      		if (name == null) {
        		//检测是否设置了useActualParamName全局配置
        		if (useActualParamName) {
          			//通过反射获取参数名称。此种方式要求JDK版本为1.8+,
          			//且要求编译时加入-parameters参数,否则获取到的参数名
          			//仍然是arg1, arg2, ..., argN
          			name = getActualParamName(method, paramIndex);
        		}
        		if (name == null) {
          			/*
   		   			 * 使用map.size()返回值作为名称,思考一下为什么不这样写:
		  			 * name = String.valueOf(paramIndex);
		  			 * 因为如果参数列表中包含 RowBounds 或 ResultHandler,这两个
		  			 * 参数会被忽略掉,这样将导致名称不连续。
		 			 * 比如参数列表 (int p1, int p2, RowBounds rb, int p3)
		  			 * - 期望得到名称列表为 ["0", "1", "2"]
	      			 * - 实际得到名称列表为 ["0", "1", "3"]
	      			 */
          			name = String.valueOf(map.size());
        		}
      		}
      		//存储paramIndex到name的映射
      		map.put(paramIndex, name);
    	}
    	names = Collections.unmodifiableSortedMap(map);
  	}

  方法参数列表解析完毕后,可得到参数下标与参数名的映射关系,这些映射关系最终存储在 ParamNameResolver的names成员变量中。

  • 4、 执行execute方法
      分析了MapperMethod的初始化过程,接下来要做的是调用MapperMethod的execute方法,执行SQL。看MapperMethod:
  	public Object execute(SqlSession sqlSession, Object[] args) {
    	Object result;
    	//根据SQL类型执行相应的数据库操作
    	switch (command.getType()) {
      		case INSERT: {
        		//对用户传入的参数进行转换,
        		Object param = method.convertArgsToSqlCommandParam(args);
        		//执行插入操作,rowCountResult方法用于处理返回值
        		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()) {
          			//如果方法返回值为void,但参数列表中包含ResultHandler,表明
	          		//使用者想通过ResultHandler的方式获取查询结果,而非通过返回值
		  			//获取结果
          			executeWithResultHandler(sqlSession, args);
          			result = null;
        		} else if (method.returnsMany()) {
          			//执行查询操作,并返回多个结果
          			result = executeForMany(sqlSession, args);
        		} else if (method.returnsMap()) {
          			//执行查询操作,并将结果封装在Map中返回
         	 		result = executeForMap(sqlSession, args);
        		} else if (method.returnsCursor()) {
          			//执行查询操作,并返回一个Cursor对象
          			result = executeForCursor(sqlSession, args);
        		} else {
          			Object param = method.convertArgsToSqlCommandParam(args);
          			//执行查询操作,并返回一个结果
          			result = sqlSession.selectOne(command.getName(), param);
          			if (method.returnsOptional()
              			&& (result == null || !method.getReturnType().equals(result.getClass()))) {
            			result = Optional.ofNullable(result);
          			}
        		}
        		break;
      			case FLUSH:
        			//执行刷新操作
        			result = sqlSession.flushStatements();
        			break;
      			default:
        			throw new BindingException("Unknown execution method for: " + command.getName());
    		}
    		//如果方法的返回值为基本类型,而返回值却为null,此种情况下应抛出异常
    		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;
	  }

  上面多次出现了convertArgsToSqlCommandParam:

    public Object convertArgsToSqlCommandParam(Object[] args) {
      	return paramNameResolver.getNamedParams(args);
    }

  接着看ParamNameResolver:

  	public Object getNamedParams(Object[] args) {
    	final int paramCount = names.size();
    	if (args == null || paramCount == 0) {
      		return null;
    	} else if (!hasParamAnnotation && paramCount == 1) {
     		/*
	  		 * 如果方法参数列表无@Param注解,且仅有一个非特别参数,则返回该
	  		 * 参数的值。比如如下方法:
	 		 * List findList(RowBounds rb, String name)
	 		 * names如:names = {1 : "0"}
	   		 * 此种情况下,返回args[names.firstKey()],即args[1] -> name
	 		 */
      		Object value = args[names.firstKey()];
      		return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
   	 	} else {
      		final Map<String, Object> param = new ParamMap<>();
      		int i = 0;
      		for (Map.Entry<Integer, String> entry : names.entrySet()) {
        		//添加<参数名, 参数值>键值对到param中
        		param.put(entry.getValue(), args[entry.getKey()]);
        		final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        		//检测names中是否包含genericParamName,什么情况下会包含?
	   			//答案:使用者显式将参数名称配置为param1,即@Param("param1")
        		if (!names.containsValue(genericParamName)) {
          			//添加<param*, value>到param中
          			param.put(genericParamName, args[entry.getKey()]);
        		}
        		i++;
      		}
      		return param;
    	}
	}

  MyBatis对哪些SQL指令提供了支持,如下:

查询语句:SELECT。
更新语句:INSERT/UPDATE/DELETE。
存储过程:CALL。

二、查询语句的执行过程

  查询语句对应的方法比较多,有如下几种:executeWithResultHandler、executeForMany、executeForMap、executeForCursor。
  这些方法在内部调用了SqlSession中的一些方法,比如selectList、selectMap、selectCursor等。这些方法的返回值类型是不同的,因此对于每种返回类型,需要有专门的处理方法。

2.1 selectOne方法

  selectOne在内部会调用selectList方法,分析selectOne方法等同于分析selectList方法。看DefaultSqlSession:

  	public <T> T selectOne(String statement, Object parameter) {
    	//调用selectList获取结果
    	List<T> list = this.selectList(statement, parameter);
    	if (list.size() == 1) {
      		//返回结果
      		return list.get(0);
    	} else if (list.size() > 1) {
      		//如果查询结果大于1则抛出异常,
      		throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    	} else {
      		return null;
    	}
  	}

  selectList方法的实现。

  	public <E> List<E> selectList(String statement, Object parameter) {
  		//调用重载方法
    	return this.selectList(statement, parameter, RowBounds.DEFAULT);
  	}

  	private final Executor executor;

  	public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    	try {
      		//获取MappedStatement
      		MappedStatement ms = configuration.getMappedStatement(statement);
      		//调用Executor实现类中的query方法
      		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();
    	}
  	}

  上面的executor变量,该变量类型为Executor。Executor是一个接口,它的实现类:

  默认情况下,executor的类型为CachingExecutor,该类是一个装饰器类,用于给目标Executor增加二级缓存功能。目标Executor默认情况下是SimpleExecutor。
  继续分析selectOne方法的调用栈。先看CachingExecutor的query方法。

  	public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    	//获取BoundSql
    	BoundSql boundSql = ms.getBoundSql(parameterObject);
    	//创建CacheKey
    	CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    	//调用重载方法
    	return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  	}

  上面的代码用于获取BoundSql对象,创建CacheKey对象,然后再将这两个对象传给重载方法。

  	public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      	throws SQLException {
    	//从MappedStatement中获取缓存
    	Cache cache = ms.getCache();
    	//若映射文件中未配置缓存或参照缓存,此时cache = null
    	if (cache != null) {
      		flushCacheIfRequired(ms);
      		if (ms.isUseCache() && resultHandler == null) {
        		ensureNoOutParams(ms, boundSql);
        		@SuppressWarnings("unchecked")
        		List<E> list = (List<E>) tcm.getObject(cache, key);
        		if (list == null) {
          			//若缓存未命中,则调用被装饰类的query方法
          			list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          			tcm.putObject(cache, key, list); 
        		}
        		return list;
      		}
    	}
    	//调用被装饰类的query方法
    	return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  	}

  以上代码涉及到了二级缓存,若二级缓存为空。若未命中,则调用被装饰类的query方法。下面来看一下BaseExecutor的中签名相同的query方法。

  	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();
      		}
      		deferredLoads.clear();
      		if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        		clearLocalCache();
      		}
    	}
    	return list;
  	}

  上面的方法主要用于从一级缓存中查找查询结果,若缓存未命中,再向数据库进行查询。在上面的代码中,出现了一个新的类DeferredLoad,这个类用于延迟加载。接下来看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 {
      		//调用doQuery进行查询
      		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;
  	}

  上面的代码仍然不是selectOne方法调用栈的终点,抛开缓存操作,queryFromDatabase最终还会调用doQuery进行查询。接下来看下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
      		StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      		//创建Statement
      		stmt = prepareStatement(handler, ms.getStatementLog());
      		//执行查询操作
      		return handler.query(stmt, resultHandler);
    	} finally {
      		//关闭Statement
      		closeStatement(stmt);
    	}
  	}

  先跳过StatementHandler和Statement创建过程。这里,以PreparedStatementHandler为例,看看它的query方法是怎样实现的。

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

  以上就是selectOne方法的执行过程。

2.2 获取BoundSql

  在执行SQL之前,需要将SQL语句完整的解析出来。SQL是配置在映射文件中的,但由于映射文件中的SQL可能会包含占位符#{},以及动态SQL标签,比如<if><where>等。因此,并不能直接使用映射文件中配置的SQL。
  MyBatis会将映射文件中的SQL解析成一组SQL片段。如果某个片段中也包含动态SQL相关的标签,那么MyBatis会对该片段再次进行分片。最终,一个 SQL配置将会被解析成一个SQL片段树。
  对SQL片段树进行解析,以便从每个片段对象中获取相应的内容。然后将这些内容组合起来即可得到一个完成的SQL语句,这个完整的SQL以及其他的一些信息最终会存储在BoundSql对象中。BoundSql类的成员变量信息:

  	private final String sql;
  	private final List<ParameterMapping> parameterMappings;
  	private final Object parameterObject;
  	private final Map<String, Object> additionalParameters;
  	private final MetaObject metaParameters;

  各个成员变量的含义:

变量名类型用途
sqlString一个完整的SQL语句,可能会包含问号?占位符
parameterMappingsList参数映射列表,SQL中的每个#{xxx} 占位符都会被解析成相应的ParameterMapping对象
parameterObjectObject运行时参数,即用户传入的参数,比如Article对象,或是其他的参数
additionalParametersMap附加参数集合,用于存储一些额外的信息,比如datebaseId等
metaParametersMetaObjectadditionalParameters的元信息对象

  接下来,开始分析BoundSql的构建过程,首先是MappedStatement的getBoundSql方法:

  	public BoundSql getBoundSql(Object parameterObject) {
    	//调用sqlSource的getBoundSql获取BoundSql
    	BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    	List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    	if (parameterMappings == null || parameterMappings.isEmpty()) {
      		//创建新的BoundSql,这里的parameterMap是ParameterMap类型。
      		//由<ParameterMap>节点进行配置,该节点已经废弃,不推荐使用。
      		//默认情况下,parameterMap.getParameterMappings()返回空集合
      		boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    	}
		//...
    	return boundSql;
  	}

  MappedStatement的getBoundSql在内部调用了SqlSource实现类的getBoundSql方法。SqlSource是一个接口,它有如下几个实现类:DynamicSqlSource、RawSqlSource、StaticSqlSource、ProviderSqlSource和VelocitySqlSource。
  前三个常用的实现类中,仅前两个实现类会在映射文件解析的过程中被使用。当SQL配置中包含${}(不是#{})占位符,或者包含<if><where>等标签时,会被认为是动态SQL,此时使用 DynamicSqlSource存储SQL片段。否则,使用RawSqlSource存储SQL配置信息。相比之下 DynamicSqlSource存储的SQL片段类型较多,解析起来也更为复杂一些。看DynamicSqlSource的 getBoundSql方法。

  	public BoundSql getBoundSql(Object parameterObject) {
    	//创建DynamicContext
    	DynamicContext context = new DynamicContext(configuration, parameterObject);
    	//解析SQL片段,并将解析结果存储到DynamicContext中
    	rootSqlNode.apply(context);
    	SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    	Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    	//构建StaticSqlSource,在此过程中将sql语句中的占位符#{}替换为问号?, 
    	//并为每个占位符构建相应的ParameterMapping
    	SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    	//调用StaticSqlSource的getBoundSql获取BoundSql
    	BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    	//将DynamicContext的ContextMap中的内容拷贝到BoundSql中
    	context.getBindings().forEach(boundSql::setAdditionalParameter);
    	return boundSql;
  	}

  DynamicSqlSource的getBoundSql方法的步骤:

  1. 创建DynamicContext。
  2. 解析SQL片段,并将解析结果存储到DynamicContext中。
  3. 解析SQL语句,并构建StaticSqlSource。
  4. 调用StaticSqlSource的getBoundSql获取BoundSql。
  5. 将DynamicContext的ContextMap中的内容拷贝到BoundSql中。
  • 1、DynamicContext
      DynamicContext是SQL语句构建的上下文,每个SQL片段解析完成后,都会将解析结果存入DynamicContext中。待所有的SQL片段解析完毕后,完整的SQL语句就会出现在DynamicContext对象中。
public class DynamicContext {

  	public static final String PARAMETER_OBJECT_KEY = "_parameter";
  	public static final String DATABASE_ID_KEY = "_databaseId";
  	private final ContextMap bindings;
  	private final StringJoiner sqlBuilder = new StringJoiner(" ");

  	public DynamicContext(Configuration configuration, Object parameterObject) {
    	//创建ContextMap
    	if (parameterObject != null && !(parameterObject instanceof Map)) {
      		MetaObject metaObject = configuration.newMetaObject(parameterObject);
      		boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
      		bindings = new ContextMap(metaObject, existsTypeHandler);
    	} else {
      		bindings = new ContextMap(null, false);
    	}
    	//存放运行时参数parameterObject以及databaseId
    	bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    	bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
  	}
}

  sqlBuilder变量用于存放SQL片段的解析结果,bindings则用于存储一些额外的信息,比如运行时参数和databaseId等。bindings类型为ContextMap,ContextMap定义在DynamicContext中,是一个静态内部类。该类继承自HashMap,并覆写了get方法。

static class ContextMap extends HashMap<String, Object> {
    private final MetaObject parameterMetaObject;

    public ContextMap(MetaObject parameterMetaObject, boolean fallbackParameterObject) {
      	this.parameterMetaObject = parameterMetaObject;
      	this.fallbackParameterObject = fallbackParameterObject;
    }

    @Override
    public Object get(Object key) {
      	String strKey = (String) key;
      	//检查是否包含strKey,若包含则直接返回
      	if (super.containsKey(strKey)) {
        	return super.get(strKey);
      	}

      	if (parameterMetaObject == null) {
        	return null;
      	}

      	if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
        	return parameterMetaObject.getOriginalObject();
      	} else {
        	//从运行时参数中查找结果
        	return parameterMetaObject.getValue(strKey);
      	}
    }
}

  DynamicContext对外提供了两个接口,用于操作sqlBuilder:

  	public String getSql() {
    	return sqlBuilder.toString().trim();
  	}

  	public void appendSql(String sql) {
    	sqlBuilder.add(sql);
  	}
  • 2、解析SQL片段
      对于一个包含了${}占位符,或<if><where>等标签的SQL,在解析的过程中,会被分解成多个片段。每个片段都有对应的类型,每种类型的片段都有不同的解析逻辑。在源码中,片段等价于sql节点,即SqlNode。SqlNode是一个接口,它有众多的实现类。

      在众多实现类中,StaticTextSqlNode用于存储静态文本,TextSqlNode用于存储带有${}占位符的文本,IfSqlNode则用于存储<if>节点的内容。MixedSqlNode内部维护了一个SqlNode集合,用于存储各种各样的SqlNode。先看MixedSqlNode:
public class MixedSqlNode implements SqlNode {
  	private final List<SqlNode> contents;

  	public MixedSqlNode(List<SqlNode> contents) {
    	this.contents = contents;
  	}

  	@Override
  	public boolean apply(DynamicContext context) {
    	//遍历SqlNode集合,调用salNode对象本身的apply方法解析sql
    	contents.forEach(node -> node.apply(context));
    	return true;
  	}
}

  MixedSqlNode可以看做是SqlNode实现类对象的容器,凡是实现了SqlNode接口的类都可以存储到MixedSqlNode中,包括它自己。MixedSqlNode解析方法apply逻辑比较简单,即遍历SqlNode集合,并调用其他SqlNode实现类对象的apply方法解析sql。再看个StaticTextSqlNode:

public class StaticTextSqlNode implements SqlNode {
  	private final String text;

  	public StaticTextSqlNode(String text) {
    	this.text = text;
  	}

 	@Override
  	public boolean apply(DynamicContext context) {
    	context.appendSql(text);
    	return true;
  	}
}

  StaticTextSqlNode用于存储静态文本,所以直接将其存储的SQL片段添加到 DynamicContext中即可。再看一下TextSqlNode:

public class TextSqlNode implements SqlNode {
  	private final String text;
  	private final Pattern injectionFilter;

  	@Override
  	public boolean apply(DynamicContext context) {
    	//创建${}占位符解析器
    	GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    	//解析${}占位符,并将解析结果添加到DynamicContext中
    	context.appendSql(parser.parse(text));
    	return true;
  	}

  	private GenericTokenParser createParser(TokenHandler handler) {
    	//创建占位符解析器,GenericTokenParser是一个通用解析器,
    	//并非只能解析${}占位符
    	return new GenericTokenParser("${", "}", handler);
  	}

  	private static class BindingTokenParser implements TokenHandler {

    	private DynamicContext context;
    	private Pattern injectionFilter;

    	public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
      		this.context = context;
      		this.injectionFilter = injectionFilter;
    	}

    	@Override
   	 	public String handleToken(String content) {
      		Object parameter = context.getBindings().get("_parameter");
      		if (parameter == null) {
        		context.getBindings().put("value", null);
      		} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
        		context.getBindings().put("value", parameter);
      		}
      		//通过ONGL从用户传入的参数中获取结果
      		Object value = OgnlCache.getValue(content, context.getBindings());
      		String srtValue = value == null ? "" : String.valueOf(value); 
      		//通过正则表达式检测srtValue有效性
      		checkInjection(srtValue);
      		return srtValue;
    	}

    	private void checkInjection(String value) {
      		if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
        		throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
      		}
    	}
	}
}

  GenericTokenParser是一个通用的标记解析器,用于解析形如${xxx}、#{xxx}等标记 。GenericTokenParser负责将标记中的内容抽取出来,并将标记内容交给相应的TokenHandler去处理。BindingTokenParser负责解析标记内容,并将解析结果返回给GenericTokenParser,用于替换${xxx}标记。
  比如:

	SELECT * FROM article WHERE author = '${author}'

  假设我们我们传入的author值为zhangsan,那么SQL最终会被解析成如下:

	SELECT * FROM article WHERE author = 'zhangsan'

  一般情况下,使用${author}接受参数都没什么问题。但可能会引起SQL注入问题,比如传入这样一个参数author=zhangsan';DELETE FROM article;,然后我们把这个参数传给TextSqlNode进行解析。得到的结果如下:

	SELECT * FROM article WHERE author = 'zhangsan'; DELETE FROM article;

   该SQL会把article表的数据清空。
  接下来看IfSqlNode:

public class IfSqlNode implements SqlNode {
  	private final ExpressionEvaluator evaluator;
  	private final String test;
  	private final SqlNode contents;

  	public IfSqlNode(SqlNode contents, String test) {
    	this.test = test;
    	this.contents = contents;
    	this.evaluator = new ExpressionEvaluator();
  	}

  	@Override
  	public boolean apply(DynamicContext context) {
    	//通过ONGL评估test表达式的结果
    	if (evaluator.evaluateBoolean(test, context.getBindings())) {
      		//若test表达式中的条件成立,则调用其他节点的apply方法进行解析
      		contents.apply(context);
      		return true;
    	}
    	return false;
  	}
}

  IfSqlNode对应的是<iftest='xxx'>节点。IfSqlNode的apply方法逻辑并不复杂,首先是通过ONGL检测test表达式是否为 true。如果为true,则调用其他节点的apply方法继续进行解析。需要注意的是<if>节点中也可嵌套其他的动态节点,并非只有纯文本。因此contents变量遍历指向的是MixedSqlNode,而非StaticTextSqlNode。
  接下来看WhereSqlNode:

public class WhereSqlNode extends TrimSqlNode {
  	//前缀列表
  	private static List<String> prefixList = Arrays.asList("AND ","OR ",
      "AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");

  	public WhereSqlNode(Configuration configuration, SqlNode contents) {
    	//调用父类的构造方法
    	super(configuration, contents, "WHERE", prefixList, null, null);
  	}
}

  在MyBatis中,WhereSqlNode和SetSqlNode都是基于TrimSqlNode实现的,WhereSqlNode对应于<where>节点。接下来看TrimSqlNode:

public class TrimSqlNode implements SqlNode {

  	private final SqlNode contents;
  	private final String prefix;
  	private final String suffix;
  	private final List<String> prefixesToOverride;
  	private final List<String> suffixesToOverride;
  	private final Configuration configuration;

  	public boolean apply(DynamicContext context) {
    	//创建具有过滤功能的DynamicContext
    	FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    	//解析节点内容
    	boolean result = contents.apply(filteredDynamicContext);
    	//过滤掉前缀和后缀
    	filteredDynamicContext.applyAll();
    	return result;
  	}
}

  apply方法首选调用了其他SqlNode的apply方法解析节点内容,这步操作完成后,FilteredDynamicContext中会得到一条SQL片段。接下里需要做的事情是过滤字符串前缀后和后缀,并添加相应的前缀和后缀。这个事情由FilteredDynamicContext负责,FilteredDynamicContext是TrimSqlNode的私有内部类。

private class FilteredDynamicContext extends DynamicContext {
    private DynamicContext delegate;
    /** 构造方法会将下面两个布尔值置为false */
    private boolean prefixApplied;
    private boolean suffixApplied;
    private StringBuilder sqlBuffer;

    public FilteredDynamicContext(DynamicContext delegate) {
      	super(configuration, null);
      	this.delegate = delegate;
      	this.prefixApplied = false;
      	this.suffixApplied = false;
      	this.sqlBuffer = new StringBuilder();
    }

    public void applyAll() {
      	sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
      	String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
      	//引用前缀和后缀,也就是对sql进行过滤操作,移除掉前缀或后缀
      	if (trimmedUppercaseSql.length() > 0) {
       	 	applyPrefix(sqlBuffer, trimmedUppercaseSql);
        	applySuffix(sqlBuffer, trimmedUppercaseSql);
      	}
      	//将当前对象的sqlBuffer内容添加到代理类中
      	delegate.appendSql(sqlBuffer.toString());
    }

    private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
      	if (!prefixApplied) {
        	//设置prefixApplied为true,以下逻辑仅会被执行一次
        	prefixApplied = true;
        	if (prefixesToOverride != null) {
          		for (String toRemove : prefixesToOverride) {
            		//检测当前sql 字符串是否包含前缀,比如 'AND ', 'AND\t'等
            		if (trimmedUppercaseSql.startsWith(toRemove)) {
              			//移除前缀
              			sql.delete(0, toRemove.trim().length());
              			break;
            		}
          		}
        	}
        	//插入前缀,比如 WHERE
        	if (prefix != null) {
          		sql.insert(0, " ");
          		sql.insert(0, prefix);
        	}
     	}
    }
    //该方法逻辑与applyPrefix大同小异
    private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
      	if (!suffixApplied) {
        	suffixApplied = true;
        	if (suffixesToOverride != null) {
          		for (String toRemove : suffixesToOverride) {
            		if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
              			int start = sql.length() - toRemove.trim().length();
              			int end = sql.length();
              			sql.delete(start, end);
              			break;
            		}
          		}
        	}
        	if (suffix != null) {
          		sql.append(" ");
          		sql.append(suffix);
        	}
      	}
    }
}

  applyAll方法的逻辑比较简单,首先从sqlBuffer中获取SQL字符串。然后调用applyPrefix和 applySuffix进行过滤操作。最后将过滤后的SQL字符串添加到被装饰的类中。applyPrefix方法会首先检测SQL字符串是不是以"AND",“OR"或"AND\n”,"OR\n"等前缀开头,若是则将前缀从sqlBuffer中移除。然后将前缀插入到sqlBuffer的首部。

  • 3、解析#{}占位符
      经过前面的解析,已经能从DynamicContext获取到完整的SQL语句。但这并不意味着解析过程就结束了,因为当前的SQL语句中还有一种占位符没有处理,即#{}。与${}占位符的处理方式不同,MyBatis并不会直接将#{}占位符替换为相应的参数值。
      #{}占位符的解析逻辑是包含在SqlSourceBuilder的parse方法中,该方法最终会将解析后的SQL以及其他的一些数据封装到StaticSqlSource中。
  	public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    	//创建#{}占位符处理器
    	ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    	//创建#{}占位符解析器
    	GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    	//解析#{}占位符,并返回解析结果
    	String sql;
    	if (configuration.isShrinkWhitespacesInSql()) {
      		sql = parser.parse(removeExtraWhitespaces(originalSql));
    	} else {
      		sql = parser.parse(originalSql);
    	}
    	//封装解析结果到StaticSqlSource中,并返回
    	return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  	}

  重点关注#{}占位符处理器ParameterMappingTokenHandler(是SqlSourceBuilder的静态内部类) 的逻辑。

private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
  	public String handleToken(String content) {
      	//获取content的对应的ParameterMapping
      	parameterMappings.add(buildParameterMapping(content));
      	return "?";
    }
}

  ParameterMappingTokenHandler的handleToken方法看起来比较简单,但实际上并非如此。GenericTokenParser负责将#{}占位符中的内容抽取出来,并将抽取出的内容传给handleToken方法。handleToken方法负责将传入的参数解析成对应的ParameterMapping对象,这步操作由buildParameterMapping方法完成。

    private ParameterMapping buildParameterMapping(String content) {
      	/*
	   	 * 将#{xxx}占位符中的内容解析成Map,过程:
	  	 * #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
      	 * 上面占位符中的内容最终会被解析成如下的结果:
     	  * {
     	  * "property": "age",
     	  * "typeHandler": "MyTypeHandler",
     	  * "jdbcType": "NUMERIC",
      	 * "javaType": "int"
      	 * }
      	 * parseParameterMapping内部依赖ParameterExpression对字符串进行解析
     	  */
      	Map<String, String> propertiesMap = parseParameterMapping(content);
      	String property = propertiesMap.get("property");
	    Class<?> propertyType;
      	//metaParameters为DynamicContext成员变量bindings的元信息对象
      	if (metaParameters.hasGetter(property)) { 
        propertyType = metaParameters.getGetterType(property);
      	/*
	  	 * parameterType是运行时参数的类型。如果用户传入的是单个参数,比如Article
	  	 * 对象,此时parameterType为Article.class。如果用户传入的多个参数,比如
	  	 * [id = 1, author = "coolblog"],MyBatis 会使用ParamMap封装这些参数,
	     * 此时parameterType 为ParamMap.class。如果parameterType有相应的
	  	 * TypeHandler,这里则把parameterType设为propertyType
     	 */
      	} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        	propertyType = parameterType;
      	} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
        	propertyType = java.sql.ResultSet.class;
      	} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
        	//如果property为空,或parameterType是Map类型,
        	//则将propertyType设为Object.class
        	propertyType = Object.class;
      	} else {
        	//表明parameterType是一个自定义的类,
        	//比如Article,此时为该类创建一个元信息对象
        	MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        	//检测参数对象有没有与property想对应的getter方法
        	if (metaClass.hasGetter(property)) {
          		//获取成员变量的类型
          		propertyType = metaClass.getGetterType(property);
        	} else {
          	propertyType = Object.class;
        	}
      	}
		// -------------------------- 分割线 ---------------------------
      	ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
      	//将propertyType赋值给javaType
      	Class<?> javaType = propertyType;
      	String typeHandlerAlias = null;
      	//遍历propertiesMap
      	for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        	String name = entry.getKey();
        	String value = entry.getValue();
        	if ("javaType".equals(name)) {
          		//如果用户明确配置了javaType,则以用户的配置为准
          		javaType = resolveClass(value);
          		builder.javaType(javaType);
        	} else if ("jdbcType".equals(name)) {
          		//解析jdbcType
          		builder.jdbcType(resolveJdbcType(value));
        	} else if ("mode".equals(name)) {
          		builder.mode(resolveParameterMode(value));
        	} else if ("numericScale".equals(name)) {
          		builder.numericScale(Integer.valueOf(value));
        	} else if ("resultMap".equals(name)) {
          		builder.resultMapId(value);
        	} else if ("typeHandler".equals(name)) {
          		typeHandlerAlias = value;
       	 	} else if ("jdbcTypeName".equals(name)) {
          		builder.jdbcTypeName(value);
        	} else if ("property".equals(name)) {
          		// Do Nothing
        	} else if ("expression".equals(name)) {
          		throw new BuilderException("Expression based parameters are not supported yet");
        	} else {
          		throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
        	}
      	}
      	if (typeHandlerAlias != null) {
        	//解析TypeHandler
        	builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
      	}
      	//构建ParameterMapping对象
      	return builder.build();
   }

  buildParameterMapping做了3件事:

  1. 解析content。
  2. 解析propertyType,对应分割线之上的代码。
  3. 构建ParameterMapping对象,对应分割线之下的代码。

  再来看一下StaticSqlSource的创建过程。

public class StaticSqlSource implements SqlSource {

  	private final String sql;
  	private final List<ParameterMapping> parameterMappings;
  	private final Configuration configuration;

  	public StaticSqlSource(Configuration configuration, String sql) {
    	this(configuration, sql, null);
  	}

  	public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    	this.sql = sql;
    	this.parameterMappings = parameterMappings;
    	this.configuration = configuration;
  	}

  	@Override
  	public BoundSql getBoundSql(Object parameterObject) {
    	//创建BoundSql对象
    	return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  	}
}
2.3 创建StatementHandler

  StatementHandler是一个核心接口,从代码分层的角度来说,StatementHandler是MyBatis源码的边界,再往下层就是JDBC层面的接口了。在执行SQL之前,StatementHandler需要创建合适的Statement对象,然后填充参数值到Statement对象中,最后通过Statement对象执行SQL,待SQL执行完毕,还要去处理查询结果等。
  最下层的三种StatementHandler实现类与三种不同的Statement进行交互。RoutingStatementHandler是一个奇怪的存在,因为JDBC中并不存在RoutingStatement。
  看一下Configuration:

  	public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    	//创建具有路由功能的StatementHandler
    	StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    	//应用插件到StatementHandler上
    	statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    	return statementHandler;
  	}

  下面看RoutingStatementHandler的代码。

public class RoutingStatementHandler implements StatementHandler {

  	private final StatementHandler delegate;

  	public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
   	 	//根据StatementType创建不同的StatementHandler
    	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());
    	}
  	}
  	//其他方法逻辑均由别的StatementHandler代理完成
}

  RoutingStatementHandler的构造方法会根据MappedStatement中的statementType变量创建不同的StatementHandler 实现类。默认情况下,statementType值为PREPARED。关于StatementHandler创建的过程就先分析到这。StatementHandler创建完成后,要做的事是创建Statement,以及将运行时参数和Statement进行绑定。

2.4 设置运行时参数到SQL中

  JDBC提供了三种Statement接口,分别是Statement、PreparedStatement和CallableStatement。

  Statement接口提供了执行SQL,获取执行结果等基本功能。PreparedStatement在此基础上,对IN类型的参数提供了支持。使得可以使用运行时参数替换SQL中的问号?占位符,而不用手动拼接 SQL。CallableStatement则是在PreparedStatement基础上,对OUT类型的参数提供了支持,该种类型的参数用于保存存储过程输出的结果。
  本节分析PreparedStatement的创建,以及设置运行时参数到SQL中的过程。Statement的创建入口是在SimpleExecutor的prepareStatement方法中:

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

  上面代码的三个步骤:

  1. 获取数据库连接。
  2. 创建Statement。
  3. 为Statement设置IN参数。

  接下来,分析PreparedStatement的创建,以及IN参数设置的过程。PreparedStatement的创建过程,在BaseStatementHandler中:

  	public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    	ErrorContext.instance().sql(boundSql.getSql());
    	Statement statement = null;
    	try {
      		//创建Statement
      		statement = instantiateStatement(connection);
      		//设置超时和FetchSize
      		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);
    	}
  	}

  再看PreparedStatementHandler:

  	protected Statement instantiateStatement(Connection connection) throws SQLException {
	   String sql = boundSql.getSql();
    	//根据条件调用不同的prepareStatement方法创建PreparedStatement
    	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() == ResultSetType.DEFAULT) {
      		return connection.prepareStatement(sql);
    	} else {
      		return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    	}
  	}

  运行时参数是如何被设置到SQL中的过程,在PreparedStatementHandler中:

  	public void parameterize(Statement statement) throws SQLException {
    	//通过参数处理器ParameterHandler设置运行时参数到PreparedStatement中
    	parameterHandler.setParameters((PreparedStatement) statement);
  	}

  再看DefaultParameterHandler:

public class DefaultParameterHandler implements ParameterHandler {

  	private final TypeHandlerRegistry typeHandlerRegistry;

  	private final MappedStatement mappedStatement;
  	private final Object parameterObject;
  	private final BoundSql boundSql;
  	private final Configuration configuration;

  	@Override
  	public void setParameters(PreparedStatement ps) {
    	ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    	//从BoundSql中获取ParameterMapping列表,每个ParameterMapping
    	//与原始SQL中的#{xxx}占位符一一对应
    	List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    	if (parameterMappings != null) {
      		for (int i = 0; i < parameterMappings.size(); i++) {
        		ParameterMapping parameterMapping = parameterMappings.get(i);
        		//检测参数类型,排除掉mode为OUT类型的parameterMapping
        		if (parameterMapping.getMode() != ParameterMode.OUT) {
          			Object value;
          			//获取属性名
          			String propertyName = parameterMapping.getProperty();
          			//检测BoundSql的additionalParameters是否包含propertyName
          			if (boundSql.hasAdditionalParameter(propertyName)) { 
            			value = boundSql.getAdditionalParameter(propertyName);
          			} else if (parameterObject == null) {
            			value = null;
          			//检测运行时参数是否有相应的类型解析器
          			} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            			//若运行时参数的类型有相应的类型处理器TypeHandler,则将
            			//parameterObject设为当前属性的值。
            			value = parameterObject;
          			} else {
            			//为用户传入的参数parameterObject创建元信息对象
            			MetaObject metaObject = configuration.newMetaObject(parameterObject);
            			//从用户传入的参数中获取propertyName对应的值
            			value = metaObject.getValue(propertyName);
          			}
          			// ---------------------分割线---------------------
          			TypeHandler typeHandler = parameterMapping.getTypeHandler();
          			JdbcType jdbcType = parameterMapping.getJdbcType();
          			if (value == null && jdbcType == null) {
            			//此处jdbcType = JdbcType.OTHER
            			jdbcType = configuration.getJdbcTypeForNull();
          			}
          			try {
            			//由类型处理器typeHandler向ParameterHandler设置参数
            			typeHandler.setParameter(ps, i + 1, value, jdbcType);
          			} catch (TypeException | SQLException e) {
            			throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          			}
        		}
      		}
    	}
  	}
}

  分割线以上的大段代码用于获取#{xxx}占位符属性所对应的运行时参数。分割线以下的代码则是获取#{xxx}占位符属性对应的TypeHandler,并在最后通过TypeHandler将运行时参数值设置到PreparedStatement中。

2.5 #{}占位符的解析与参数的设置过程

  假设有这样一条SQL语句:

	SELECT * FROM author WHERE name = #{name} AND age = #{age}

  这个SQL语句中包含两个#{}占位符,在运行时这两个占位符会被解析成两个ParameterMapping对象:

ParameterMapping{property='name', mode=IN,
	javaType=class java.lang.String, jdbcType=null, ...}
ParameterMapping{property='age', mode=IN,
	javaType=class java.lang.Integer, jdbcType=null, ...}

  #{xxx}占位符解析完毕后,得到的SQL:

	SELECT * FROM Author WHERE name = ? AND age = ?

  假设下面这个方法与上面的SQL对应:

	Author findByNameAndAge(@Param("name")String name, @Param("age")Integer age)

  该方法的参数列表会被ParamNameResolver解析成一个map:

	{
		0: "name",
		1: "age"
	}

  假设该方法在运行时有如下的调用:

	findByNameAndAge("zhangsan", 20)

  此时,需要再次借助ParamNameResolver的力量。这次我们将参数名和运行时的参数值绑定起来,得到如下的映射关系。

	{
		"name": "zhangsan",
		"age": 20,
		"param1": "zhangsan",
		"param2": 20
	}

  下一步,要将运行时参数设置到SQL中。由于原SQL经过解析后,占位符信息已经被擦除掉了,无法直接将运行时参数注入到SQL中。不过,这些占位符信息被记录在ParameterMapping中,MyBatis会将ParameterMapping会按照#{}占位符的解析顺序存入到List中。这样通过ParameterMapping 在列表中的位置确定它与SQL中的哪一个个?占位符相关联。同时通过ParameterMapping中的property字段,我们可以到“参数名与参数值”映射表中查找具体的参数值。这样,就可以将参数值准确的设置到SQL中了,此时SQL如下:

	SELECT * FROM Author WHERE name = "zhangsan" AND age = 20

  当运行时参数被设置到SQL中后,下一步要做的事情是执行SQL,然后处理SQL执行结果。对于更新操作,数据库一般返回一个int行数值,表示受影响行数,这个处理起来比较简单。接下来,就来看看MyBatis是如何处理查询结果的。

2.6 处理查询结果

  MyBatis可以将查询结果,即结果集ResultSet自动映射成实体类对象。这样使用者就无需再手动操作结果集,并将数据填充到实体类对象中。
  在MyBatis中,结果集的处理工作由结果集处理器ResultSetHandler执行。ResultSetHandler是一个接口,它只有一个实现类DefaultResultSetHandler。结果集的处理入口方法是 handleResultSets:

  	public List<Object> handleResultSets(Statement stmt) throws SQLException {
    	ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    	final List<Object> multipleResults = new ArrayList<>();

   	 	int resultSetCount = 0;
    	//获取第一个结果集
    	ResultSetWrapper rsw = getFirstResultSet(stmt);

    	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);
      		//获取下一个结果集
      		rsw = getNextResultSet(stmt);
      		cleanUpAfterHandlingResultSet();
      		resultSetCount++;
    	}

    	String[] resultSets = mappedStatement.getResultSets();
    	if (resultSets != null) {
      		while (rsw != null && resultSetCount < resultSets.length) {
        		ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        		if (parentMapping != null) {
          			String nestedResultMapId = parentMapping.getNestedResultMapId();
          			ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          			handleResultSet(rsw, resultMap, null, parentMapping);
        		}
        		rsw = getNextResultSet(stmt);
        		cleanUpAfterHandlingResultSet();
        		resultSetCount++;
      		}
    	}
    	return collapseSingleResultList(multipleResults);
  	}

  	private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    	//获取结果集
    	ResultSet rs = stmt.getResultSet();
    	while (rs == null) {
      		/*
	   		 * 移动ResultSet指针到下一个上,有些数据库驱动可能需要使用者
	   	 	 * 先调用getMoreResults方法,然后才能调用getResultSet方法
	  		 * 获取到第一个ResultSet
	 		 */
      		if (stmt.getMoreResults()) {
        		rs = stmt.getResultSet();
      		} else {
        		if (stmt.getUpdateCount() == -1) {
          			// no more results. Must be no resultset
          			break;
        		}
      		}
    	}
    	/*
		 * 这里并不直接返回ResultSet,而是将其封装到ResultSetWrapper中。
		 * ResultSetWrapper中包含了ResultSet一些元信息,比如列名称、
		 * 每列对应的JdbcType、以及每列对应的Java类名(class name,譬如
		 * java.lang.String)等。
		 */
    	return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  	}

  该方法首先从Statement中获取第一个结果集,然后调用handleResultSet方法对该结果集进行处理。
  对结果集的处理逻辑:

  	private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    	try {
      		if (parentMapping != null) {
        		//多结果集相关逻辑
        		handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      		} else {
        		/*
        		 * 检测resultHandler是否为空。ResultHandler是一个接口,使用者可
				 * 实现该接口,这样我们可以通过ResultHandler自定义接收查询结果的
				 * 动作。
	    		 */
        		if (resultHandler == null) {
          			//创建默认的结果处理器
          			DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          			//处理结果集的行数据
          			handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          			multipleResults.add(defaultResultHandler.getResultList());
        		} else {
          			//处理结果集的行数据
          			handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        		}
      		}
    	} finally {
      		closeResultSet(rsw.getResultSet());
    	}
  	}

  handleRowValues方法,该方法用于处理结果集中的数据。

  	public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    	if (resultMap.hasNestedResultMaps()) {
      		ensureNoRowBounds();
      		checkResultHandler();
      		//处理嵌套映射
      		handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    	} else {
      		//处理简单映射
      		handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    	}
  	}

  handleRowValues方法中针对两种映射方式进行了处理。一种是嵌套映射,另一种是简单映射。本文所说的嵌套查询是指<ResultMap>中嵌套了一个<ResultMap>。简单映射的处理逻辑:

  	private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    	DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    	ResultSet resultSet = rsw.getResultSet();
    	//根据RowBounds定位到指定行记录
    	skipRows(resultSet, rowBounds);
    	//检测是否还有更多行的数据需要处理
   		while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      		//获取经过鉴别器处理后的ResultMap
      		ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      		//从resultSet中获取结果
      		Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      		//存储结果
      		storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    	}
  	}

  上面方法的逻辑:

  1. 根据RowBounds定位到指定行记录。
  2. 循环处理多行数据。
  3. 使用鉴别器处理ResultMap。
  4. 映射ResultSet,得到映射结果rowValue。
  5. 存储结果。

  第一个步骤对应的代码逻辑:

  	private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    	//检测rs的类型,不同的类型行数据定位方式是不同的
    	if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
      		if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
        		//直接定位到rowBounds.getOffset()位置处
        		rs.absolute(rowBounds.getOffset());
      		}
    	} else {
      		for (int i = 0; i < rowBounds.getOffset(); i++) {
        		/*
		 		 * 通过多次调用rs.next()方法实现行数据定位。
				 * 当Offset数值很大时,这种效率很低下
				 */
        		if (!rs.next()) {
          			break;
        		}
      		}
    	}
  	}

  MyBatis默认提供了RowBounds用于分页,这并非是一个高效的分页方式。除了使用 RowBounds,还可以使用一些第三方分页插件进行分页。ResultSet的映射过程:

  	private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    	final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    	//创建实体类对象,比如Article对象
    	Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    	if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      		final MetaObject metaObject = configuration.newMetaObject(rowValue);
      		boolean foundValues = this.useConstructorMappings;
      		//检测是否应该自动映射结果集
      		if (shouldApplyAutomaticMappings(resultMap, false)) {
        		//进行自动映射
        		foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      		}
      		//根据<resultMap>节点中配置的映射关系进行映射
      		foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      		foundValues = lazyLoader.size() > 0 || foundValues;
      		rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    	}
    	return rowValue;
  	}

  上面的方法中的重要逻辑已经注释出来了,这里再简单总结一下:

  1. 创建实体类对象。
  2. 检测结果集是否需要自动映射,若需要则进行自动映射。
  3. <resultMap>中配置的映射关系进行映射。

  来按顺序进行分节说明。首先分析实体类的创建过程。

  • 1、创建实体类对象
      MyBatis创建实体类对象的过程在DefaultResultSetHandler中:
  	private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
   	 	this.useConstructorMappings = false; 
    	final List<Class<?>> constructorArgTypes = new ArrayList<>();
    	final List<Object> constructorArgs = new ArrayList<>();
    	//调用重载方法创建实体类对象
    	Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    	//检测实体类是否有相应的类型处理器
    	if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      		final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      		for (ResultMapping propertyMapping : propertyMappings) {
        		//如果开启了延迟加载,则为resultObject生成代理类
        		if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
          			//创建代理类,默认使用Javassist框架生成代理类。由于实体类通常
		  			//不会实现接口,所以不能使用JDK动态代理API为实体类生成代理。
          			resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          			break;
        		}
      		}
    	}
    	this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();
    	return resultObject;
  	}

  创建实体类对象的逻辑被封装在了createResultObject的重载方法中。创建好实体类对后,还需要对<resultMap>中配置的映射信息进行检测。若发现有关联查询,且关联查询结果的加载方式为延迟加载,此时需为实体类生成代理类。createResultObject重载方法的逻辑:

  	private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
      throws SQLException {
    	final Class<?> resultType = resultMap.getType();
    	final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
   	 	//获取<constructor>节点对应的ResultMapping
    	final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    	//检测是否有与返回值类型相对应的TypeHandler,若有则直接从
		//通过TypeHandler从结果集中ᨀ取数据,并生成返回值对象
    	if (hasTypeHandlerForResultObject(rsw, resultType)) {
      		//通过TypeHandler获取ᨀ取,并生成返回值对象
      		return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    	} else if (!constructorMappings.isEmpty()) {
      		//通过<constructor>节点配置的映射信息从ResultSet中取数据,
	  		//然后将这些数据传给指定构造方法,即可创建实体类对象
      		return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    	} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
      		//通过ObjectFactory调用目标类的默认构造方法创建实例
      		return objectFactory.create(resultType);
    	} else if (shouldApplyAutomaticMappings(resultMap, false)) {
      		//通过自动映射查找合适的构造方法创建实例
      		return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
    	}
    	throw new ExecutorException("Do not know how to create an instance of " + resultType);
  	}

  createResultObject方法中包含了4种创建实体类对象的方式。一般情况下,若无特殊要求,MyBatis会通过ObjectFactory调用默认构造方法创建实体类对象。ObjectFactory是一个接口,开发者可以实现这个接口,以按照自己的逻辑控制对象的创建过程。至此,实体类对象创建好了,接下里要做的事情是将结果集中的数据映射到实体类对象中。

  • 2、结果集映射
      在MyBatis中,结果集自动映射有三种等级:

NONE - 禁用自动映射。仅设置手动映射属性。
PARTIAL - 将自动映射结果除了那些有内部定义内嵌结果映射的(joins)。
FULL - 自动映射所有。

  除了以上三种等级,还可以显示配置<resultMap>节点的autoMapping属性,以启用或者禁用指定ResultMap的自动映射设定。下面,来看一下自动映射相关的逻辑。

  	private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
    	//检测<resultMap> 是否配置了autoMapping属性
    	if (resultMap.getAutoMapping() != null) {
      		//返回autoMapping属性
      		return resultMap.getAutoMapping();
    	} else {
      		if (isNested) {
        		//对于嵌套resultMap,仅当全局的映射行为为FULL时,才进行自动映射
        		return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
      		} else {
        		//对于普通的resultMap,只要全局的映射行为不为NONE,即可进行自动映射
        		return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
      		}
    	}
  	}

  shouldApplyAutomaticMappings方法用于检测是否应为当前结果集应用自动映射。检测结果取决于<resultMap>节点的autoMapping属性,以及全局自动映射行为。
  看下MyBatis是如何进行自动映射的。

  	private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    	//获取UnMappedColumnAutoMapping列表
    	List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    	boolean foundValues = false;
    	if (!autoMapping.isEmpty()) {
      		for (UnMappedColumnAutoMapping mapping : autoMapping) {
        		//通过TypeHandler从结果集中获取指定列的数据
        		final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        		if (value != null) {
          			foundValues = true;
        		}
       	 		if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          			//通过元信息对象设置value到实体类对象的指定字段上
          			metaObject.setValue(mapping.property, value);
        		}
      		}
    	}
    	return foundValues;
  	}

  applyAutomaticMappings方法的逻辑:首先是获取UnMappedColumnAutoMapping集合,然后遍历该集合,并通过TypeHandler从结果集中获取数据,最后再将获取到的数据设置到实体类对象中。
  UnMappedColumnAutoMapping用于记录未配置在<resultMap>节点中的映射关系。该类定义在DefaultResultSetHandler内部:

private static class UnMappedColumnAutoMapping {
    private final String column;
    private final String property;
    private final TypeHandler<?> typeHandler;
    private final boolean primitive;

    public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
      	this.column = column;
      	this.property = property;
      	this.typeHandler = typeHandler;
      	this.primitive = primitive;
    }
}

  UnMappedColumnAutoMapping,仅用于记录映射关系。获取UnMappedColumnAutoMapping集合的过程。

  	private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    	final String mapKey = resultMap.getId() + ":" + columnPrefix;
    	//从缓存中获取UnMappedColumnAutoMapping列表
    	List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    	//缓存未命中
    	if (autoMapping == null) {
      		autoMapping = new ArrayList<>();
      		//从ResultSetWrapper中获取未配置在<resultMap>中的列名
      		final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      		for (String columnName : unmappedColumnNames) {
        		String propertyName = columnName;
        		if (columnPrefix != null && !columnPrefix.isEmpty()) {
          			if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
            			//获取不包含列名前缀的属性名
            			propertyName = columnName.substring(columnPrefix.length());
          			} else {
            			continue;
          			}
        		}
        		//将下划线形式的列名转成驼峰式,比如AUTHOR_NAME -> authorName
        		final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
        		if (property != null && metaObject.hasSetter(property)) {
          			//检测当前属性是否存在于resultMap中
          			if (resultMap.getMappedProperties().contains(property)) {
            			continue;
          			}
          			//获取属性对于的类型
          			final Class<?> propertyType = metaObject.getSetterType(property);
          			if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
            			//获取类型处理器
            			final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
            			//封装上面获取到的信息到UnMappedColumnAutoMapping对象中
            			autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
          			} else {
            			configuration.getAutoMappingUnknownColumnBehavior()
                			.doAction(mappedStatement, columnName, property, propertyType);
          			}
        		} else {
          			// 若property为空,或实体类中无property属性,此时无法完成
		  			// 列名与实体类属性建立映射关系。针对这种情况,有三种处理方式,
		  			// 1. 什么都不做
		  			// 2. 仅打印日志
		  			// 3. 抛出异常
		  			// 默认情况下,是什么都不做
          			configuration.getAutoMappingUnknownColumnBehavior()
              			.doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
        		}
      		}
      		//写入缓存
      		autoMappingsCache.put(mapKey, autoMapping);
    	}
    	return autoMapping;
  	}

  该方法的逻辑:

  1. 从ResultSetWrapper中获取未配置在<resultMap>中的列名。
  2. 遍历上一步获取到的列名列表。
  3. 若列名包含列名前缀,则移除列名前缀,得到属性名。
  4. 将下划线形式的列名转成驼峰式。
  5. 获取属性类型。
  6. 获取类型处理器。
  7. 创建UnMappedColumnAutoMapping实例。

  来分析第一个步骤的逻辑,在ResultSetWrapper中:

  	public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
    	List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
    	if (unMappedColumnNames == null) {
      		//加载已映射与未映射列名
      		loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
      		//获取未映射列名
      		unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
    	}
    	return unMappedColumnNames;
  	}

  	private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
    	List<String> mappedColumnNames = new ArrayList<>();
    	List<String> unmappedColumnNames = new ArrayList<>();
    	final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
    	//为<resultMap>中的列名拼接前缀
    	final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
    	//遍历columnNames,columnNames是ResultSetWrapper的成员变量,
		//保存了当前结果集中的所有列名
    	for (String columnName : columnNames) {
      		final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
      		// 检测已映射列名集合中是否包含当前列名
      		if (mappedColumns.contains(upperColumnName)) {
        		mappedColumnNames.add(upperColumnName);
      		} else {
        		//将列名存入unmappedColumnNames中
        		unmappedColumnNames.add(columnName);
      		}
    	}
    	//缓存列名集合
    	mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
    	unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
  	}

  首先是从当前数据集中获取列名集合,然后获取<resultMap>中配置的列名集合。之后遍历数据集中的列名集合,并判断列名是否被配置在了<resultMap>节点中。若配置了,则表明该列名已有映射关系,此时该列名存入mappedColumnNames中。若未配置,则表明列名未与实体类的某个字段形成映射关系,此时该列名存入unmappedColumnNames中。
  自动映射的分析就先到这,接下来分析一下MyBatis是如何将结果集中的数据填充到已映射的实体类字段中的。依然在DefaultResultSetHandler中:

  	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;
    	//获取ResultMapping
    	final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    	for (ResultMapping propertyMapping : propertyMappings) {
      		//拼接列名前缀,得到完整列名
      		String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      		if (propertyMapping.getNestedResultMapId() != null) {
        		column = null;
     		}
      		/*
	  		 * 下面的if分支由三个或条件组合而成,三个条件的含义如下:
	  		 * 条件一:检测column是否为 {prop1=col1, prop2=col2} 形式,该种形式的column一般用于关联查询
	  		 * 条件二:检测当前列名是否被包含在已映射的列名集合中,若包含则可进行数据集映射操作
	  		 * 条件三:多结果集相关
	 		 */
      		if (propertyMapping.isCompositeResult()
          		|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          		|| propertyMapping.getResultSet() != null) {
        		//从结果集中获取指定列的数据
        		Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        		final String property = propertyMapping.getProperty();
        		if (property == null) {
          			continue;
        		//若获取到的值为DEFERED,则延迟加载该值
        		} else if (value == DEFERRED) {
	         		foundValues = true;
          			continue;
        		}
        		if (value != null) {
          			foundValues = true;
        		}
        		if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          			//将获取到的值设置到实体类对象中
          			metaObject.setValue(property, value);
        		}
      		}
    	}
    	return foundValues;
  	}

  	private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    	if (propertyMapping.getNestedQueryId() != null) {
      		//获取关联查询结果
      		return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    	} else if (propertyMapping.getResultSet() != null) {
      		addPendingChildRelation(rs, metaResultObject, propertyMapping);   
      		return DEFERRED;
    	} else {
      		final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      		//拼接前缀
      		final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      		//从ResultSet中获取指定列的值
      		return typeHandler.getResult(rs, column);
    	}
  	}

  applyPropertyMappings方法首先从ResultSetWrapper中获取已映射列名集合mappedColumnNames, 从ResultMap获取映射对象ResultMapping集合。然后遍历ResultMapping集合,在此过程中调用getPropertyMappingValue获取指定指定列的数据,最后将获取到的数据设置到实体类对象中。到此,基本的结果集映射过程就分析完了。

  • 3、关联查询与延迟加载
      MyBatis提供了两个标签用于支持一对一和一对多的使用场景,分别是<association><collection>
      看MyBatis是如何实现关联查询的,从DefaultResultSetHandler中的getNestedQueryMappingValue 方法开始分析:
  	private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    	//获取关联查询id,id = 命名空间 + <association> 的select属性值
    	final String nestedQueryId = propertyMapping.getNestedQueryId();
    	final String property = propertyMapping.getProperty();
   	 	//根据nestedQueryId获取MappedStatement
    	final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    	final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    	/*
	 	 * 生成关联查询语句参数对象,参数类型可能是一些包装类,Map 或是自定义的实体类,
		 * 具体类型取决于配置信息。以上面的例子为基础,下面分析不同配置对
		 * 参数类型的影响:
		 * 1. <association column="author_id">
		 * column 属性值仅包含列信息,参数类型为 author_id 列对应的类型,
		 * 这里为 Integer
		 * 2. <association column="{id=author_id, name=title}">
		 * column 属性值包含了属性名与列名的复合信息,MyBatis 会根据列名从
		 * ResultSet 中获取列数据,并将列数据设置到实体类对象的指定属性中,比如:
		 * Author{id=1, name="MyBatis 源码分析系列文章导读", age=null, …}
		 * 或是以键值对 <属性, 列数据> 的形式,将两者存入 Map 中。比如:
		 * {"id": 1, "name": "MyBatis 源码分析系列文章导读"}
		 *
		 * 至于参数类型到底为实体类还是 Map,取决于关联查询语句的配置信息。比如:
		 * <select id="findAuthor"> -> 参数类型为 Map
		 * <select id="findAuthor" parameterType="Author">
		 * -> 参数类型为实体类
		 */
    	final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
    	Object value = null;
    	if (nestedQueryParameterObject != null) {
      		//获取BoundSql
      		final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
      		final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
      		final Class<?> targetType = propertyMapping.getJavaType();
      		//检查一级缓存是否保存了关联查询结果
      		if (executor.isCached(nestedQuery, key)) {
        		//从一级缓存中获取关联查询的结果,并通过metaResultObject
				//将结果设置到相应的实体类对象中
        		executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
        		value = DEFERRED;
      		} else {
        		//创建结果加载器
        		final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
        		//检测当前属性是否需要延迟加载
        		if (propertyMapping.isLazy()) {
          			//添加延迟加载相关的对象到loaderMap集合中
          			lazyLoader.addLoader(property, metaResultObject, resultLoader);
          			value = DEFERRED;
        		} else {
          			//直接执行关联查询
          			value = resultLoader.loadResult();
        		}
      		}
    	}
   	 	return value;
  	}

  该方法的逻辑:

  1. 根据nestedQueryId获取MappedStatement。
  2. 生成参数对象。
  3. 获取BoundSql。
  4. 检测一级缓存中是否有关联查询的结果,若有,则将结果设置到实体类对象中。
  5. 若一级缓存未命中,则创建结果加载器ResultLoader。
  6. 检测当前属性是否需要进行延迟加载,若需要,则添加延迟加载相关的对象到loaderMap集合中。
  7. 如不需要延迟加载,则直接通过结果加载器加载结果。

  getNestedQueryMappingValue方法中逻辑多是都是和延迟加载有关。除了延迟加载,以上流程中针对一级缓存的检查是十分有必要的,若缓存命中,可直接取用结果,无需再在执行关联查询 SQL。若缓存未命中,接下来就要按部就班执行延迟加载相关逻辑。
  接下来,分析一下MyBatis延迟加载是如何实现的。首先来看一下添加延迟加载相关对象到loaderMap集合中的逻辑,在ResultLoaderMap中:

  	public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
    	//将属性名转为大写
    	String upperFirst = getUppercaseFirstProperty(property);
    	if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
      		throw new ExecutorException("Nested lazy loaded result property '" + property
              + "' for query id '" + resultLoader.mappedStatement.getId()
              + " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");
    	}
    	//创建LoadPair,并将<大写属性名,LoadPair对象>键值对添加到loaderMap中
    	loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
  	}

  addLoader方法的参数最终都传给了LoadPair,该类的load方法会在内部调用ResultLoader 的loadResult方法进行关联查询,并通过metaResultObject将查询结果设置到实体类对象中。那LoadPair的load方法由谁调用呢?答案是实体类的代理对象。
  MyBatis会为需要延迟加载的类生成代理类,代理逻辑会拦截实体类的方法调用。默认情况下,MyBatis会使用Javassist为实体类生成代理,代理逻辑封装在JavassistProxyFactory类中:

    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
      	final String methodName = method.getName();
      	try {
        	synchronized (lazyLoader) {
          		if (WRITE_REPLACE_METHOD.equals(methodName)) {
					//针对writeReplace方法的处理逻辑,与延迟加载无关
         		} else {
            		if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
              			//如果aggressive为true,或触发方法(比如equals,
			  			//hashCode等)被调用,则加载所有的所有延迟加载的数据
              			if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                			lazyLoader.loadAll();
              			} else if (PropertyNamer.isSetter(methodName)) {
                			final String property = PropertyNamer.methodToProperty(methodName);
                			//如果使用者显示调用了setter方法,则将相应的延迟加载类从loaderMap 中移除
                			lazyLoader.remove(property);
                			//检测使用者是否调用getter方法
              			} else if (PropertyNamer.isGetter(methodName)) {
                			final String property = PropertyNamer.methodToProperty(methodName);
                			//检测该属性是否有相应的LoadPair对象
                			if (lazyLoader.hasLoader(property)) {
                  				//执行延迟加载逻辑
                  				lazyLoader.load(property);
                			}
              			}
            		}
          		}
        	}
        	//调用被代理类的方法
        	return methodProxy.invoke(enhanced, args);
      	} catch (Throwable t) {
        	throw ExceptionUtil.unwrapThrowable(t);
      	}
     }
  }

  代理方法首先会检查aggressive是否为true ,如果不满足,再去检lazyLoadTriggerMethods 是否包含当前方法名。这里两个条件只要一个为true,当前实体类中所有需要延迟加载。aggressive和lazyLoadTriggerMethods两个变量的值取决于下面的配置。

	<setting name="aggressiveLazyLoading" value="false"/>
	<setting name="lazyLoadTriggerMethods" value="equals,hashCode"/>

  回到上面的代码中。如果执行线程未进入第一个条件分支,那么紧接着,代理逻辑会检查使用者是不是调用了实体类的setter方法。如果调用了,就将该属性对应的LoadPair从loaderMap中移除。为什么要这么做呢?答案是:使用者既然手动调用setter方法,说明使用者想自定义某个属性的值。此时,延迟加载逻辑不应该再修改该属性的值,所以这里从loaderMap中移除属性对应的LoadPair。最后如果使用者调用的是某个属性的getter方法,且该属性配置了延迟加载,此时延迟加载逻辑就会被触发。
  接下来,来看看延迟加载逻辑是怎样实现的,在ResultLoaderMap。

  	public boolean load(String property) throws SQLException {
    	//从loaderMap中移除property所对应的LoadPair
    	LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
    	if (pair != null) {
      		//加载结果
      		pair.load();
      		return true;
    	}
    	return false;
  	}

  接下来看LoadPair(ResultLoaderMap的静态内部类):

    public void load() throws SQLException {
      	if (this.metaResultObject == null) {
        	throw new IllegalArgumentException("metaResultObject is null");
      	}
      	if (this.resultLoader == null) {
        	throw new IllegalArgumentException("resultLoader is null");
      	}
	  	//调用重载方法
      	this.load(null);
    }

    public void load(final Object userObject) throws SQLException {
      	// 若 metaResultObject 和 resultLoader 为 null,则创建相关对象。
	  	// 在当前调用情况下,两者均不为 null,条件不成立。
      	if (this.metaResultObject == null || this.resultLoader == null) {
        	if (this.mappedParameter == null) {
          		throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                  + "required parameter of mapped statement ["
                  + this.mappedStatement + "] is not serializable.");
        	}

       	 	final Configuration config = this.getConfiguration();
        	final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
        	if (ms == null) {
          		throw new ExecutorException("Cannot lazy load property [" + this.property
                  + "] of deserialized object [" + userObject.getClass()
                  + "] because configuration does not contain statement ["
                  + this.mappedStatement + "]");
        	}

        	this.metaResultObject = config.newMetaObject(userObject);
        	this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                metaResultObject.getSetterType(this.property), null, null);
      	}

      	//线程安全检测
      	if (this.serializationCheck == null) {
        	//重新创建新的ResultLoader和ClosedExecutor,
	    	//ClosedExecutor是非线程安全的
        	final ResultLoader old = this.resultLoader;
        	this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
                old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
      	}
	  	//调用ResultLoader的loadResult方法加载结果,
	  	//并通过metaResultObject设置结果到实体类对象中
      	this.metaResultObject.setValue(property, this.resultLoader.loadResult());
   }

  下面看一下ResultLoader的loadResult方法逻辑是怎样的。

  	public Object loadResult() throws SQLException {
    	List<Object> list = selectList();
    	resultObject = resultExtractor.extractObjectFromList(list, targetType);
    	return resultObject;
  	}

  	private <E> List<E> selectList() throws SQLException {
    	Executor localExecutor = executor;
    	if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
      		localExecutor = newExecutor();
    	}
    	try {
      		//通过Executor就行查询
      		return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
    	} finally {
      		if (localExecutor != executor) {
        		localExecutor.close(false);
      		}
    	}
  	}

  在ResultLoader中终于看到了执行关联查询的代码,即selectList方法中的逻辑。该方法在内部通过 Executor 进行查询。

  • 4、存储映射结果
      存储映射结果是“查询结果”处理流程中的最后一环,实际上也是查语句执行过程的最后一环。重新回到DefaultResultSetHandler:
  	private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    	if (parentMapping != null) {
      		//多结果集相关
      		linkToParents(rs, parentMapping, rowValue);
    	} else {
      		//存储结果
      		callResultHandler(resultHandler, resultContext, rowValue);
    	}
  	}

 	private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    	//设置结果到resultContext中
    	resultContext.nextResultObject(rowValue);
    	//从resultContext获取结果,并存储到resultHandler中
    	((ResultHandler<Object>) resultHandler).handleResult(resultContext);
  	}

  上面方法显示将rowValue设置到ResultContext中,然后再将ResultContext对象作为参数传给ResultHandler的handleResult方法。
  分别看一下ResultContext和ResultHandler的实现类。DefaultResultContext:

public class DefaultResultContext<T> implements ResultContext<T> {

  	private T resultObject;
  	private int resultCount;
  	/** 状态字段 */
  	private boolean stopped;

  	//...

  	@Override
  	public boolean isStopped() {
    	return stopped;
 	}

  	public void nextResultObject(T resultObject) {
    	resultCount++;
    	this.resultObject = resultObject;
  	}

  	@Override
  	public void stop() {
    	this.stopped = true;
  	}
}

  DefaultResultContext中包含了一个状态字段,表明结果上下文的状态。在处理多行数据时,MyBatis会检查该字段的值,已决定是否需要进行后续的处理。
  下面再来看一下 DefaultResultHandler 的源码。

public class DefaultResultHandler implements ResultHandler<Object> {

  	private final List<Object> list;

  	public DefaultResultHandler() {
    	list = new ArrayList<>();
  	}

  	@SuppressWarnings("unchecked")
  	public DefaultResultHandler(ObjectFactory objectFactory) {
    	list = objectFactory.create(List.class);
  	}

  	@Override
  	public void handleResult(ResultContext<?> context) {
    	//添加结果到list中
    	list.add(context.getResultObject());
  	}

  	public List<Object> getResultList() {
    	return list;
  	}
}

  DefaultResultHandler默认使用List存储结果。除此之外,如果Mapper(或Dao)接口方法返回值为Map类型,此时则需要另一种ResultHandler实现类处理结果,即DefaultMapResultHandler。

三、更新语句的执行过程

  执行更新语句所需处理的情况较之查询语句要简单不少,两者最大的区别更新语句的执行结果类型单一,处理逻辑要简单不少。除此之外,两者在缓存的处理上也有比较大的区别。更新过程会立即刷新缓存,而查询过程则不会。下面开始分析更新语句的执行过程。

3.1 更新语句执行过程全貌

  从MapperMethod的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:
				//...
        		break;
      		case FLUSH:
        		//...
        		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;
  	}

  插入、更新以及删除操作最终都调用了SqlSession接口中的方法。这三个方法返回值均是受影响行数,是一个整型值。rowCountResult方法负责处理这个整型值。下面分析SqlSession的实现类DefaultSqlSession的代码。

  	public int insert(String statement, Object parameter) {
    	return update(statement, parameter);
  	}

  	public int delete(String statement, Object parameter) {
    	return update(statement, parameter);
  	}

  	public int update(String statement, Object parameter) {
    	try {
     	 	dirty = true;
      		//获取MappedStatement
      		MappedStatement ms = configuration.getMappedStatement(statement);
      		//调用Executor的update方法
      		return executor.update(ms, wrapCollection(parameter));
    	} catch (Exception e) {
      		throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    	} finally {
      		ErrorContext.instance().reset();
    	}
  	}

  insert和delete方法最终都调用了同一个update方法。下面分析Executor的update方法。先看CachingExecutor:

  	public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    	//刷新二级缓存
    	flushCacheIfRequired(ms);
    	return delegate.update(ms, parameterObject);
  	}

  再看BaseExecutor:

  	public int update(MappedStatement ms, Object parameter) throws SQLException {
    	ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    	if (closed) {
      		throw new ExecutorException("Executor was closed.");
    	}
    	//刷新一级缓存
    	clearLocalCache();
    	return doUpdate(ms, parameter);
  	}

  Executor实现类中的方法在进行下一步操作之前,都会先刷新各自的缓存。默认情况下,insert、update和delete操作都会清空一二级缓存。下面分析doUpdate方法,该方法是一个抽象方法,到BaseExecutor的子类SimpleExecutor中看看该方法是如何实现的。

  	public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    	Statement stmt = null;
    	try {
      		Configuration configuration = ms.getConfiguration();
      		//创建StatementHandler
      		StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      		//创建Statement
      		stmt = prepareStatement(handler, ms.getStatementLog());
      		//调用StatementHandler的update方法
      		return handler.update(stmt);
    	} finally {
     	 	closeStatement(stmt);
    	}
  	}

  下面分析PreparedStatementHandler的update方法。

  	public int update(Statement statement) throws SQLException {
    	PreparedStatement ps = (PreparedStatement) statement;
    	//执行SQL
    	ps.execute();
    	//返回受影响行数
    	int rows = ps.getUpdateCount();
    	//获取用户传入的参数值,参数值类型可能是普通的实体类,也可能是Map
    	Object parameterObject = boundSql.getParameterObject();
    	KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    	//获取自增主键的值,并将值填入到参数对象中
    	keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    	return rows;
  	}

  PreparedStatementHandler的update方法的逻辑比较清晰明了,更新语句的SQL会在此方法中被执行。执行结果为受影响行数,对于insert语句,有时候我们还想获取自增主键的值,因此需要进行一些额外的操作。这些额外操作的逻辑封装在KeyGenerator的实现类中,下面我们一起看一下 KeyGenerator 的实现逻辑。

3.2 KeyGenerator

  KeyGenerator是一个接口,目前它有三个实现类:Jdbc3KeyGenerator、SelectKeyGenerator和NoKeyGenerator。
  Jdbc3KeyGenerator 用于获取插入数据后的自增主键数值。某些数据库不支持自增主键,需要手动填写主键字段,此时需要借助SelectKeyGenerator获取主键值。至于NoKeyGenerator,这是一个空实现,没什么可说的。
  先看Jdbc3KeyGenerator:

  	public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    	// do nothing
  	}

  	public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    	processBatch(ms, stmt, parameter);
  	}

  	public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
    	//获取主键字段
    	final String[] keyProperties = ms.getKeyProperties();
    	if (keyProperties == null || keyProperties.length == 0) {
      		return;
    	}
    	try (ResultSet rs = stmt.getGeneratedKeys()) {
      		//获取结果集ResultSet的元数据
      		final ResultSetMetaData rsmd = rs.getMetaData();
      		final Configuration configuration = ms.getConfiguration();
      		if (rsmd.getColumnCount() < keyProperties.length) {
      			//ResultSet中数据的列数要大于等于主键的数量
      		} else {
        		assignKeys(configuration, rs, rsmd, keyProperties, parameter);
      		}
   	 	} catch (Exception e) {
      		throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    	}
  	}

  	private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
      Object parameter) throws SQLException {
    	if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
      		assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
    	} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
        && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
      		assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
    	} else {
      		assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
    	}
  	}

  Jdbc3KeyGenerator的processBefore方法是一个空方法,processAfter则是一个空壳方法,只有一行代码。Jdbc3KeyGenerator的重点在processBatch方法中,由于存在批量插入的情况,所以该方法的名字类包含batch单词,表示可处理批量插入的结果集。

3.3 处理更新结果

  更新语句的执行结果是一个整型值,表示本次更新所影响的行数。由于返回值类型简单,因此处理逻辑也很简单。先看MapperMethod:

  	private Object rowCountResult(int rowCount) {
    	final Object result;
    	//这里的method类型为MethodSignature,即方法签名
    	if (method.returnsVoid()) {
     		//方法返回类型为void,则不用返回结果,这里将结果置空
      		result = null;
    	} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      		//方法返回类型为Integer或int,直接赋值返回即可
      		result = rowCount;
    	} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      		//如果返回值类型为Long或者long,这里强转一下即可
      		result = (long) rowCount;
    	} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      		//方法返回类型为布尔类型,若rowCount>0,则返回ture,否则返回false
      		result = rowCount > 0;
    	} else {
     	 	throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    	}
    	return result;
  	}

四、SQL 执行过程总结


  在MyBatis中,SQL执行过程的实现代码是有层次的,每层都有相应的功能。比如,SqlSession是对外接口的接口,因此它提供了各种语义清晰的方法,供使用者调用。
  Executor层做的事情较多,比如一二级缓存功能就是嵌入在该层内的。
  StatementHandler 层主要是与JDBC层面的接口打交道。至于ParameterHandler和ResultSetHandler,一个负责向SQL中设置运行时参数,另一个负责处理SQL执行结果,它们俩可以看做是StatementHandler辅助类。最后看一下右边横跨数层的类,Configuration是一个全局配置类,很多地方都依赖它。MappedStatement对应SQL配置,包含了SQL配置的相关信息。BoundSql中包含了已完成解析的SQL语句,以及运行时参数等。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值