上一篇咱们分析了Mapper接口代理类的生成,本篇接着分析是如何调用到XML中的SQLjava
咱们回顾一下MapperMethod 的execute方法正则表达式
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()); } return result; }
本节选择分析 selectOne 方法,主要是由于 selectOne 在内部会调用 selectList 方法。同时分析 selectOne 方法等同于分析 selectList 方法。代码以下sql
// 执行查询操做,并返回一个结果 result = sqlSession.selectOne(command.getName(), param);
咱们看到是经过sqlSession来执行查询的,而且传入的参数为command.getName()和param,也就是namespace.methodName(mapper.EmployeeMapper.getAll)和方法的运行参数。咱们知道了,全部的数据库操做都是交给sqlSession来执行的,那咱们就来看看sqlSession的方法数据库
DefaultSqlSessionexpress
public <T> T selectOne(String statement, Object parameter) { // 调用 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 方法的实现。缓存
DefaultSqlSessionapp
private final Executor executor; public <E> List<E> selectList(String statement, Object parameter) { // 调用重载方法 return this.selectList(statement, parameter, RowBounds.DEFAULT); } 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去执行了ide
咱们回顾一下前面的文章,Executor的建立过程,代码以下ui
//建立一个执行器,默认是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方法了this
CachingExecutor
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 类的成员变量信息,以下:
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 BoundSql getBoundSql(Object parameterObject) { // 调用 sqlSource 的 getBoundSql 获取 BoundSql,把method运行时参数传进去 BoundSql boundSql = sqlSource.getBoundSql(parameterObject);return boundSql; }
MappedStatement 的 getBoundSql 在内部调用了 SqlSource 实现类的 getBoundSql 方法,并把method运行时参数传进去,SqlSource 是一个接口,它有以下几个实现类:
当 SQL 配置中包含 ${}
(不是 #{})占位符,或者包含 <if>、<where> 等标签时,会被认为是动态 SQL,此时使用 DynamicSqlSource 存储 SQL 片断。不然,使用 RawSqlSource 存储 SQL 配置信息。咱们来看看DynamicSqlSource的getBoundSql
DynamicSqlSource
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; }
该方法由数个步骤组成,这里总结一下:
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) { this.bindings.put(name, value); } //拼接Sql片断 public void appendSql(String sql) { this.sqlBuilder.append(sql); this.sqlBuilder.append(" "); } //获得sql字符串 public String getSql() { return this.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) { // 从运行时参数中查找结果,这里会在${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)); // 经过正则表达式检测 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' and id= 1
很明显这种直接拼接值很容易形成SQL注入,假如咱们传入的参数为name值为 chenhao'; DROP TABLE user;# ,解析获得的结果为
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 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()); } public String handleToken(String content) { // 获取 content 的对应的 ParameterMapping parameterMappings.add(buildParameterMapping(content)); // 返回 ? return "?"; }
咱们看到将Sql中的 #{} 占位符替换成"?",而且将对应的参数转化成ParameterMapping 对象,经过buildParameterMapping 完成,最后建立一个StaticSqlSource,将sql字符串和ParameterMappings为参数传入,返回这个StaticSqlSource
private ParameterMapping buildParameterMapping(String content) { /* * 将#{xxx} 占位符中的内容解析成 Map。 * #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler} * 上面占位符中的内容最终会被解析成以下的结果: * { * "property": "age", * "typeHandler": "MyTypeHandler", * "jdbcType": "NUMERIC", * "javaType": "int" * } */ 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 是运行时参数的类型。若是用户传入的是单个参数,好比 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)) {...} else if ("numericScale".equals(name)) {...} else if ("resultMap".equals(name)) {...} else if ("typeHandler".equals(name)) { typeHandlerAlias = value; } else if ("jdbcTypeName".equals(name)) {...} else if ("property".equals(name)) {...} 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 String sql; private List<ParameterMapping> parameterMappings; private Object parameterObject; private Map<String, Object> additionalParameters; private 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(); this.metaParameters = configuration.newMetaObject(this.additionalParameters); } public String getSql() { return this.sql; } //略 }
咱们看到只是作简单的赋值。BoundSql中包含了sql,#{}解析成的parameterMappings,还有运行时参数parameterObject。好了,SQL解析咱们就介绍这么多。咱们先回顾一下咱们代码是从哪里开始的
CachingExecutor
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 2 // 获取 BoundSql 3 BoundSql boundSql = ms.getBoundSql(parameterObject); 4 // 建立 CacheKey 5 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); 6 // 调用重载方法 7 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 8 }
如上,咱们刚才都是分析的第三行代码,获取到了BoundSql,CacheKey 和二级缓存有关,咱们留在下一篇文章单独来说,接着咱们看第七行重载方法 query
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); 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
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 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; }
从一级缓存中查找查询结果。若缓存未命中,再向数据库进行查询。至此咱们明白了一级二级缓存的大概思路,先从二级缓存中查找,若未命中二级缓存,再从一级缓存中查找,若未命中一级缓存,再从数据库查询数据,那咱们来看看是怎么从数据库查询的
BaseExecutor
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 <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有什么做用呢?经过这个对象获取Statement对象,而后填充运行时参数,最后调用query完成查询。咱们来看看其建立过程
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 的建立过程
咱们看到statementType 的默认类型为PREPARED,这里将会建立PreparedStatementHandler。
接着咱们看下面一行代码prepareStatement,
建立 Statement 在 stmt = prepareStatement(handler, ms.getStatementLog()); 这句代码,那咱们跟进去看看
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,最后就能够执行查询操做了。因为篇幅的缘由,咱们留在下一篇文章再来详细讲解