前言

上一篇我们分析了Mapper接口代理类的生成,本篇接着分析是如何调用到XML中的SQL

我们回顾一下MapperMethod 的execute方法

public class MapperMethod {
    
    //包含SQL相关信息,比喻MappedStatement的id属性,(mapper.UserMapper.getAll)
    private final SqlCommand command;

    //包含了关于执行的Mapper方法的参数类型和返回类型。
    private final MethodSignature method;

    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()) {
                    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);
                }
                break;
            case FLUSH:
                // 执行刷新操作
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName() 
            + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }
}

selectOne 方法分析

本节选择分析 selectOne 方法,主要是因为 selectOne 在内部会调用 selectList 方法。同时分析 selectOne 方法等同于分析 selectList 方法。代码如下

result = sqlSession.selectOne(command.getName(), param);

我们看到是通过sqlSession来执行查询的,并且传入的参数为command.getName()和param,也就是namespace.methodName(mapper.UserMapper.getAll)和方法的运行参数。我们知道了,所有的数据库操作都是交给sqlSession来执行的,那我们就来看看sqlSession的方法

public class DefaultSqlSession implements SqlSession {

	@Override
	public <T> T selectOne(String statement, Object parameter) {
		// Popular vote was to return null on 0 results and throw exception on too many.
		// 调用 selectList 获取结果
		List<T> list = this.<T>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;
		}
	}
}

如上,selectOne 方法在内部调用 selectList 了方法,并取 selectList 返回值的第1个元素作为自己的返回值。如果 selectList 返回的列表元素大于1,则抛出异常。下面我们来看看 selectList 方法的实现。

DefaultSqlSession

public class DefaultSqlSession implements SqlSession {

	private final Configuration configuration;
	private final Executor executor;

	@Override
	public <E> List<E> selectList(String statement, Object parameter) {
		// 调用重载方法
		return this.selectList(statement, parameter, RowBounds.DEFAULT);
	}
	
	@Override
	public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
		try {
			// 通过MappedStatement的Id获取 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();
		}
	}
}

我们之前创建DefaultSqlSession的时候,是创建了一个Executor的实例作为其属性的,我们看到通过MappedStatement的Id获取 MappedStatement后,就交由Executor去执行了

我们回顾一下前面的文章,Executor的创建过程,代码如下

public class Configuration {

    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

    public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
    }

    //创建一个执行器,默认是SIMPLE
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        //根据executorType来创建相应的执行器,Configuration默认是SIMPLE
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            //创建SimpleExecutor实例,并且包含Configuration和transaction属性
            executor = new SimpleExecutor(this, transaction);
        }
        
        //如果要求缓存,生成另一种CachingExecutor,装饰者模式,默认都是返回CachingExecutor
        /**
         * 二级缓存开关配置示例
         * <settings>
         *   <setting name="cacheEnabled" value="true"/>
         * </settings>
         */
        if (cacheEnabled) {
            //CachingExecutor使用装饰器模式,将executor的功能添加上了二级缓存的功能,二级缓存会单独文章来讲
            executor = new CachingExecutor(executor);
        }
        
        //此处调用插件,通过插件可以改变Executor行为,此处我们后面单独文章讲
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
}

executor包含了Configuration和Transaction,默认的执行器为SimpleExecutor,如果开启了二级缓存(默认开启),则CachingExecutor会包装SimpleExecutor,那么我们该看CachingExecutor的query方法了

public class CachingExecutor implements Executor {

	@Override
	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 对象,然后再将这两个对象传给重载方法。CacheKey 以及接下来即将出现的一二级缓存将会独立成文进行分析。

获取 BoundSql

// 获取 BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);

调用了MappedStatement的getBoundSql方法,并将运行时参数传入其中,我们大概的猜一下,这里是不是拼接SQL语句呢,并将运行时参数设置到SQL语句中?

 

我们都知道 SQL 是配置在映射文件中的,但由于映射文件中的 SQL 可能会包含占位符 #{},以及动态 SQL 标签,比如 <if>、<where> 等。因此,我们并不能直接使用映射文件中配置的 SQL。MyBatis 会将映射文件中的 SQL 解析成一组 SQL 片段。我们需要对这一组片段进行解析,从每个片段对象中获取相应的内容。然后将这些内容组合起来即可得到一个完成的 SQL 语句,这个完整的 SQL 以及其他的一些信息最终会存储在 BoundSql 对象中。下面我们来看一下 BoundSql 类的成员变量信息,如下:

public class BoundSql {

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

下面用一个表格列举各个成员变量的含义。

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

接下来我们接着MappedStatement 的 getBoundSql 方法,代码如下:

public final class MappedStatement {

	public BoundSql getBoundSql(Object parameterObject) {
	
		// 调用 sqlSource 的 getBoundSql 获取 BoundSql,把method运行时参数传进去
		BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings == null || parameterMappings.isEmpty()) {
			boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
		}

		// check for nested result maps in parameter mappings (issue #30)
		for (ParameterMapping pm : boundSql.getParameterMappings()) {
			String rmId = pm.getResultMapId();
			if (rmId != null) {
				ResultMap rm = configuration.getResultMap(rmId);
				if (rm != null) {
					hasNestedResultMaps |= rm.hasNestedResultMaps();
				}
			}
		}

		return boundSql;
	}
}

MappedStatement 的 getBoundSql 在内部调用了 SqlSource 实现类的 getBoundSql 方法,并把method运行时参数传进去,SqlSource 是一个接口,它有如下几个实现类:

  • DynamicSqlSource
  • RawSqlSource
  • StaticSqlSource
  • ProviderSqlSource
  • VelocitySqlSource

当 SQL 配置中包含 ${}(不是 #{})占位符,或者包含 <if>、<where> 等标签时,会被认为是动态 SQL,此时使用 DynamicSqlSource 存储 SQL 片段。否则,使用 RawSqlSource 存储 SQL 配置信息。我们来看看DynamicSqlSource的getBoundSql

DynamicSqlSource

public class DynamicSqlSource implements SqlSource {

	private final Configuration configuration;
	private final SqlNode rootSqlNode;

	@Override
	public BoundSql getBoundSql(Object parameterObject) {
		// 创建 DynamicContext
		DynamicContext context = new DynamicContext(configuration, parameterObject);
		
		// 解析 SQL 片段,并将解析结果存储到 DynamicContext 中,
		// 这里会将${}替换成method对应的运行时参数,也会解析<if><where>等SqlNode
		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 中
		for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
			boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
		}
		return boundSql;
	}
}

该方法由数个步骤组成,这里总结一下:

  • 1、创建 DynamicContext
  • 2、解析 SQL 片段,并将解析结果存储到 DynamicContext 中
  • 3、解析 SQL 语句,并构建 StaticSqlSource
  • 4、调用 StaticSqlSource 的 getBoundSql 获取 BoundSql
  • 5、将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql

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";

	//bindings 则用于存储一些额外的信息,比如运行时参数
	private final ContextMap bindings;
	
	//sqlBuilder 变量用于存放 SQL 片段的解析结果
	private final StringBuilder sqlBuilder = new StringBuilder();

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

	public void bind(String name, Object value) {
		bindings.put(name, value);
	}

	//拼接Sql片段
	public void appendSql(String sql) {
		sqlBuilder.append(sql);
		sqlBuilder.append(" ");
	}

	//得到sql字符串
	public String getSql() {
		return sqlBuilder.toString().trim();
	}

	//继承HashMap
	static class ContextMap extends HashMap<String, Object> {

		private MetaObject parameterMetaObject;
		
		public ContextMap(MetaObject parameterMetaObject) {
			this.parameterMetaObject = parameterMetaObject;
		}

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

			if (parameterMetaObject != null) {
				// issue #61 do not modify the context when reading
				// 从运行时参数中查找结果,这里会在${name}解析时,通过name获取运行时参数值,替换掉${name}字符串
				return parameterMetaObject.getValue(strKey);
			}

			return null;
		}
	}
	
	// 省略部分代码

}  

解析 SQL 片段

接着我们来看看解析SQL片段的逻辑

rootSqlNode.apply(context);

对于一个包含了 ${} 占位符,或 <if>、<where> 等标签的 SQL,在解析的过程中,会被分解成多个片段。每个片段都有对应的类型,每种类型的片段都有不同的解析逻辑。在源码中,片段这个概念等价于 sql 节点,即 SqlNode。

StaticTextSqlNode 用于存储静态文本,TextSqlNode 用于存储带有 ${} 占位符的文本,IfSqlNode 则用于存储 <if> 节点的内容。MixedSqlNode 内部维护了一个 SqlNode 集合,用于存储各种各样的 SqlNode。接下来,我将会对 MixedSqlNode 、StaticTextSqlNode、TextSqlNode、IfSqlNode、WhereSqlNode 以及 TrimSqlNode 等进行分析

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 集合
		for (SqlNode sqlNode : contents) {
			// 调用 salNode 对象本身的 apply 方法解析 sql
			sqlNode.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) {
		//直接拼接当前sql片段的文本到DynamicContext的sqlBuilder中
		context.appendSql(text);
		return true;
	}

}

StaticTextSqlNode 用于存储静态文本,直接将其存储的 SQL 的文本值拼接到 DynamicContext 的sqlBuilder中即可。下面分析一下 TextSqlNode。

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));
		
		// 解析 ${} 占位符,通过ONGL 从用户传入的参数中获取结果,替换text中的${} 占位符
        // 并将解析结果的文本拼接到DynamicContext的sqlBuilder中
		context.appendSql(parser.parse(text));
		return true;
	}

	private GenericTokenParser createParser(TokenHandler handler) {
		// 创建占位符解析器
		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)); // issue #274 return "" instead of "null"
			
			// 通过正则表达式检测 srtValue 有效性
			checkInjection(srtValue);
			return srtValue;
		}
	}
}	

GenericTokenParser 是一个通用的标记解析器,用于解析形如 ${name},#{id} 等标记。此时是解析 ${name}的形式,从运行时参数的Map中获取到key为name的值,直接用运行时参数替换掉 ${name}字符串,将替换后的text字符串拼接到DynamicContext的sqlBuilder中

 

举个例子吧,比喻我们有如下SQL

SELECT * FROM user WHERE name = '${name}' and id= ${id}

假如我们传的参数 Map中name值为 chenhao,id为1,那么该 SQL 最终会被解析成如下的结果:

SELECT * FROM user WHERE name = 'chenhao'; DROP TABLE user;#'

由于传入的参数没有经过转义,最终导致了一条 SQL 被恶意参数拼接成了两条 SQL。这就是为什么我们不应该在 SQL 语句中是用 ${} 占位符,风险太大。接着我们来看看IfSqlNode

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 方法进行解析
            // 如果是静态SQL节点,则会直接拼接到DynamicContext中
			contents.apply(context);
			return true;
		}
		return false;
	}

}

IfSqlNode 对应的是 <if test='xxx'> 节点,首先是通过 ONGL 检测 test 表达式是否为 true,如果为 true,则调用其子节点的 apply 方法继续进行解析。如果子节点是静态SQL节点,则子节点的文本值会直接拼接到DynamicContext中

 

好了,其他的SqlNode我就不一一分析了,大家有兴趣的可以去看看

解析 #{} 占位符

经过前面的解析,我们已经能从 DynamicContext 获取到完整的 SQL 语句了。但这并不意味着解析过程就结束了,因为当前的 SQL 语句中还有一种占位符没有处理,即 #{}。与 ${} 占位符的处理方式不同,MyBatis 并不会直接将 #{} 占位符替换为相应的参数值,而是将其替换成?。其解析是在如下代码中实现的

SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

我们看到将前面解析过的sql字符串和运行时参数的Map作为参数,我们来看看parse方法

public class SqlSourceBuilder extends BaseBuilder {

	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 = parser.parse(originalSql);
		
		// 封装解析结果到 StaticSqlSource 中,并返回,因为所有的动态参数都已经解析了,可以封装成一个静态的SqlSource
		return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
	}
	
	private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

		private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
		private Class<?> parameterType;
		private MetaObject metaParameters;

		public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
			super(configuration);
			this.parameterType = parameterType;
			this.metaParameters = configuration.newMetaObject(additionalParameters);
		}

		public List<ParameterMapping> getParameterMappings() {
			return parameterMappings;
		}

		@Override
		public String handleToken(String content) {
			// 获取 content 的对应的 ParameterMapping
			parameterMappings.add(buildParameterMapping(content));
			// 返回 ?
			return "?";
		}
	}
}

我们看到将Sql中的 #{} 占位符替换成"?",并且将对应的参数转化成ParameterMapping 对象,通过buildParameterMapping 完成,最后创建一个StaticSqlSource,将sql字符串和ParameterMappings为参数传入,返回这个StaticSqlSource

public class SqlSourceBuilder extends BaseBuilder {
	
	private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

		private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
		private Class<?> parameterType;
		private MetaObject metaParameters;


		/*
		 * 将#{xxx} 占位符中的内容解析成 Map。
		 *   #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
		 *      上面占位符中的内容最终会被解析成如下的结果:
		 *  {
		 *      "property": "age",
		 *      "typeHandler": "MyTypeHandler", 
		 *      "jdbcType": "NUMERIC", 
		 *      "javaType": "int"
		 *  }
		 */
		private ParameterMapping buildParameterMapping(String content) {
			Map<String, String> propertiesMap = parseParameterMapping(content);
			String property = propertiesMap.get("property");
			Class<?> propertyType;
			
			// metaParameters 为 DynamicContext 成员变量 bindings 的元信息对象
			if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
				propertyType = metaParameters.getGetterType(property);
				
			/*
			 * parameterType 是运行时参数的类型。如果用户传入的是单个参数,比如 Employe 对象,此时 
			 * parameterType 为 Employe.class。如果用户传入的多个参数,比如 [id = 1, author = "chenhao"],
			 * MyBatis 会使用 ParamMap 封装这些参数,此时 parameterType 为 ParamMap.class。
			 */
			} 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)) {
				propertyType = Object.class;
			} else {
			
			/*
			 * 代码逻辑走到此分支中,表明 parameterType 是一个自定义的类,
			 * 比如 Employe,此时为该类创建一个元信息对象
			 */
			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 " + parameterProperties);
				}
			}
			if (typeHandlerAlias != null) {
				builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
			}
			
			// 构建 ParameterMapping 对象
			return builder.build();
		}
	}
}

SQL 中的 #{name, ...} 占位符被替换成了问号 ?。#{name, ...} 也被解析成了一个 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);
	}

}

最后我们通过创建的StaticSqlSource就可以获取BoundSql对象了,并传入运行时参数

BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

也就是调用上面创建的StaticSqlSource 中的getBoundSql方法,这是简单的 return new BoundSql(configuration, sql, parameterMappings, parameterObject); ,接着看看BoundSql

public class BoundSql {

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

	public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
		this.sql = sql;
		this.parameterMappings = parameterMappings;
		this.parameterObject = parameterObject;
		this.additionalParameters = new HashMap<String, Object>();
		this.metaParameters = configuration.newMetaObject(additionalParameters);
	}

	public String getSql() {
		return sql;
	}
	
	//略......
}

我们看到只是做简单的赋值。BoundSql中包含了sql,#{}解析成的parameterMappings,还有运行时参数parameterObject。好了,SQL解析我们就介绍这么多。我们先回顾一下我们代码是从哪里开始的

CachingExecutor

public class CachingExecutor implements Executor {

	@Override
	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 和二级缓存有关,我们留在下一篇文章单独来讲,接着我们看第七行重载方法 query

public class CachingExecutor implements Executor {

	private final Executor delegate;
	private final TransactionalCacheManager tcm = new TransactionalCacheManager();

	@Override
	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 方法,也就是SimpleExecutor的query方法
					list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
					tcm.putObject(cache, key, list); // issue #578 and #116
				}
				return list;
			}
		}
		
		// 调用被装饰类的 query 方法,这里的delegate我们知道应该是SimpleExecutor
		return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
	}
}

上面的代码涉及到了二级缓存,若二级缓存为空,或未命中,则调用被装饰类的 query 方法。被装饰类为SimpleExecutor,而SimpleExecutor继承BaseExecutor,那我们来看看 BaseExecutor 的query方法

BaseExecutor.query

public abstract class BaseExecutor implements Executor {

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

从一级缓存中查找查询结果。若缓存未命中,再向数据库进行查询。至此我们明白了一级二级缓存的大概思路,先从二级缓存中查找,若未命中二级缓存,再从一级缓存中查找,若未命中一级缓存,再从数据库查询数据,那我们来看看是怎么从数据库查询的

BaseExecutor.queryFromDatabase

public abstract class BaseExecutor implements Executor {

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

}

调用了doQuery方法进行查询,最后将查询结果放入一级缓存,我们来看看doQuery,在SimpleExecutor中

SimpleExecutor

public abstract class BaseExecutor implements Executor {

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

}


public class SimpleExecutor extends BaseExecutor {

	@Override
	public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
		Statement stmt = null;
		try {
			Configuration configuration = ms.getConfiguration();
			// 创建 StatementHandler
			StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
			// 创建 Statement
			stmt = prepareStatement(handler, ms.getStatementLog());
			// 执行查询操作
			return handler.<E>query(stmt, resultHandler);
		} finally {
			// 关闭 Statement
			closeStatement(stmt);
		}
	}
}

我们先来看看第一步创建StatementHandler

创建StatementHandler

StatementHandler有什么作用呢?通过这个对象获取Statement对象,然后填充运行时参数,最后调用query完成查询。我们来看看其创建过程

public class Configuration {

	protected final InterceptorChain interceptorChain = new InterceptorChain();

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

	}
}

RoutingStatementHandler 的构造方法会根据 MappedStatement 中的 statementType 变量创建不同的 StatementHandler 实现类。那statementType 是什么呢?我们还要回顾一下MappedStatement 的创建过程

public final class MappedStatement {

	public static class Builder {
		private MappedStatement mappedStatement = new MappedStatement();

		public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
			mappedStatement.configuration = configuration;
			mappedStatement.id = id;
			mappedStatement.sqlSource = sqlSource;
			
			mappedStatement.statementType = StatementType.PREPARED;
			mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
			mappedStatement.resultMaps = new ArrayList<ResultMap>();
			mappedStatement.sqlCommandType = sqlCommandType;
			mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
			String logId = id;
			if (configuration.getLogPrefix() != null) {
				logId = configuration.getLogPrefix() + id;
			}
			mappedStatement.statementLog = LogFactory.getLog(logId);
			mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
		}
	}
}

我们看到statementType 的默认类型为PREPARED,这里将会创建PreparedStatementHandler。

接着我们看下面一行代码prepareStatement,

创建 Statement

创建 Statement 在 stmt = prepareStatement(handler, ms.getStatementLog());这句代码,那我们跟进去看看

public class SimpleExecutor extends BaseExecutor {

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

在上面的代码中我们终于看到了和jdbc相关的内容了,创建完Statement,最后就可以执行查询操作了。由于篇幅的原因,我们留在下一篇文章再来详细讲解

 

参考: https://www.cnblogs.com/java-chen-hao/p/11754184.html