前言
上一篇我們分析了Mapper接口代理類的生成珠十,本篇接著分析是如何調(diào)用到XML中的SQL
我們回顧一下MapperMethod 的execute方法
public class MapperMethod {
//包含SQL相關(guān)信息医舆,比喻MappedStatement的id屬性瞻讽,(mapper.UserMapper.getAll)
private final SqlCommand command;
//包含了關(guān)于執(zhí)行的Mapper方法的參數(shù)類型和返回類型。
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根據(jù) SQL 類型執(zhí)行相應(yīng)的數(shù)據(jù)庫(kù)操作
switch (command.getType()) {
case INSERT: {
// 對(duì)用戶傳入的參數(shù)進(jìn)行轉(zhuǎn)換菩鲜,下同
Object param = method.convertArgsToSqlCommandParam(args);
// 執(zhí)行插入操作勾效,rowCountResult 方法用于處理返回值
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 執(zhí)行更新操作
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 執(zhí)行刪除操作
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 根據(jù)目標(biāo)方法的返回類型進(jìn)行相應(yīng)的查詢操作
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 執(zhí)行查詢操作各吨,并返回多個(gè)結(jié)果
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 執(zhí)行查詢操作,并將結(jié)果封裝在 Map 中返回
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 執(zhí)行查詢操作沦寂,并返回一個(gè) Cursor 對(duì)象
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 執(zhí)行查詢操作学密,并返回一個(gè)結(jié)果
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
// 執(zhí)行刷新操作
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 方法分析
本節(jié)選擇分析 selectOne 方法,主要是因?yàn)?selectOne 在內(nèi)部會(huì)調(diào)用 selectList 方法传藏。同時(shí)分析 selectOne 方法等同于分析 selectList 方法腻暮。代碼如下
result = sqlSession.selectOne(command.getName(), param);
我們看到是通過(guò)sqlSession來(lái)執(zhí)行查詢的彤守,并且傳入的參數(shù)為command.getName()和param,也就是namespace.methodName(mapper.UserMapper.getAll)和方法的運(yùn)行參數(shù)哭靖。我們知道了具垫,所有的數(shù)據(jù)庫(kù)操作都是交給sqlSession來(lái)執(zhí)行的,那我們就來(lái)看看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.
// 調(diào)用 selectList 獲取結(jié)果
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
// 返回結(jié)果
return list.get(0);
} else if (list.size() > 1) {
// 如果查詢結(jié)果大于1則拋出異常
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
}
如上试幽,selectOne 方法在內(nèi)部調(diào)用 selectList 了方法筝蚕,并取 selectList 返回值的第1個(gè)元素作為自己的返回值。如果 selectList 返回的列表元素大于1铺坞,則拋出異常起宽。下面我們來(lái)看看 selectList 方法的實(shí)現(xiàn)。
DefaultSqlSession
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 調(diào)用重載方法
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 通過(guò)MappedStatement的Id獲取 MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 調(diào)用 Executor 實(shí)現(xiàn)類中的 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();
}
}
}
我們之前創(chuàng)建DefaultSqlSession的時(shí)候济榨,是創(chuàng)建了一個(gè)Executor的實(shí)例作為其屬性的坯沪,我們看到通過(guò)MappedStatement的Id獲取 MappedStatement后,就交由Executor去執(zhí)行了
我們回顧一下前面的文章擒滑,Executor的創(chuàng)建過(guò)程腐晾,代碼如下
public class Configuration {
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
//創(chuàng)建一個(gè)執(zhí)行器,默認(rèn)是SIMPLE
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//根據(jù)executorType來(lái)創(chuàng)建相應(yīng)的執(zhí)行器,Configuration默認(rèn)是SIMPLE
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//創(chuàng)建SimpleExecutor實(shí)例丐一,并且包含Configuration和transaction屬性
executor = new SimpleExecutor(this, transaction);
}
//如果要求緩存藻糖,生成另一種CachingExecutor,裝飾者模式,默認(rèn)都是返回CachingExecutor
/**
* 二級(jí)緩存開(kāi)關(guān)配置示例
* <settings>
* <setting name="cacheEnabled" value="true"/>
* </settings>
*/
if (cacheEnabled) {
//CachingExecutor使用裝飾器模式,將executor的功能添加上了二級(jí)緩存的功能钝诚,二級(jí)緩存會(huì)單獨(dú)文章來(lái)講
executor = new CachingExecutor(executor);
}
//此處調(diào)用插件,通過(guò)插件可以改變Executor行為颖御,此處我們后面單獨(dú)文章講
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
executor包含了Configuration和Transaction,默認(rèn)的執(zhí)行器為SimpleExecutor凝颇,如果開(kāi)啟了二級(jí)緩存(默認(rèn)開(kāi)啟)潘拱,則CachingExecutor會(huì)包裝SimpleExecutor,那么我們?cè)摽碈achingExecutor的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);
// 創(chuàng)建 CacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 調(diào)用重載方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
上面的代碼用于獲取 BoundSql 對(duì)象拧略,創(chuàng)建 CacheKey 對(duì)象芦岂,然后再將這兩個(gè)對(duì)象傳給重載方法。CacheKey 以及接下來(lái)即將出現(xiàn)的一二級(jí)緩存將會(huì)獨(dú)立成文進(jìn)行分析垫蛆。
獲取 BoundSql
// 獲取 BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
調(diào)用了MappedStatement的getBoundSql方法禽最,并將運(yùn)行時(shí)參數(shù)傳入其中,我們大概的猜一下袱饭,這里是不是拼接SQL語(yǔ)句呢川无,并將運(yùn)行時(shí)參數(shù)設(shè)置到SQL語(yǔ)句中?
我們都知道 SQL 是配置在映射文件中的虑乖,但由于映射文件中的 SQL 可能會(huì)包含占位符 #{}懦趋,以及動(dòng)態(tài) SQL 標(biāo)簽,比如 <if>疹味、<where> 等仅叫。因此帜篇,我們并不能直接使用映射文件中配置的 SQL。MyBatis 會(huì)將映射文件中的 SQL 解析成一組 SQL 片段诫咱。我們需要對(duì)這一組片段進(jìn)行解析笙隙,從每個(gè)片段對(duì)象中獲取相應(yīng)的內(nèi)容。然后將這些內(nèi)容組合起來(lái)即可得到一個(gè)完成的 SQL 語(yǔ)句坎缭,這個(gè)完整的 SQL 以及其他的一些信息最終會(huì)存儲(chǔ)在 BoundSql 對(duì)象中竟痰。下面我們來(lái)看一下 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;
}
下面用一個(gè)表格列舉各個(gè)成員變量的含義幻锁。
變量名 | 類型 | 用途 |
---|---|---|
sql | String | 一個(gè)完整的 SQL 語(yǔ)句凯亮,可能會(huì)包含問(wèn)號(hào) ? 占位符 |
parameterMappings | List | 參數(shù)映射列表,SQL 中的每個(gè) #{xxx} 占位符都會(huì)被解析成相應(yīng)的 ParameterMapping 對(duì)象 |
parameterObject | Object | 運(yùn)行時(shí)參數(shù)哄尔,即用戶傳入的參數(shù)宽堆,比如 Article 對(duì)象瓷马,或是其他的參數(shù) |
additionalParameters | Map | 附加參數(shù)集合,用于存儲(chǔ)一些額外的信息,比如 datebaseId 等 |
metaParameters | MetaObject | additionalParameters 的元信息對(duì)象 |
接下來(lái)我們接著MappedStatement 的 getBoundSql 方法靠胜,代碼如下:
public final class MappedStatement {
public BoundSql getBoundSql(Object parameterObject) {
// 調(diào)用 sqlSource 的 getBoundSql 獲取 BoundSql凰棉,把method運(yùn)行時(shí)參數(shù)傳進(jìn)去
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 在內(nèi)部調(diào)用了 SqlSource 實(shí)現(xiàn)類的 getBoundSql 方法档桃,并把method運(yùn)行時(shí)參數(shù)傳進(jìn)去昧廷,SqlSource 是一個(gè)接口,它有如下幾個(gè)實(shí)現(xiàn)類:
- DynamicSqlSource
- RawSqlSource
- StaticSqlSource
- ProviderSqlSource
- VelocitySqlSource
當(dāng) SQL 配置中包含 ${}(不是 #{})占位符窄锅,或者包含 <if>创千、<where> 等標(biāo)簽時(shí),會(huì)被認(rèn)為是動(dòng)態(tài) SQL入偷,此時(shí)使用 DynamicSqlSource 存儲(chǔ) SQL 片段追驴。否則,使用 RawSqlSource 存儲(chǔ) SQL 配置信息疏之。我們來(lái)看看DynamicSqlSource的getBoundSql
DynamicSqlSource
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 創(chuàng)建 DynamicContext
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 解析 SQL 片段殿雪,并將解析結(jié)果存儲(chǔ)到 DynamicContext 中,
// 這里會(huì)將${}替換成method對(duì)應(yīng)的運(yùn)行時(shí)參數(shù)锋爪,也會(huì)解析<if><where>等SqlNode
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
/*
* 構(gòu)建 StaticSqlSource丙曙,在此過(guò)程中將 sql 語(yǔ)句中的占位符 #{} 替換為問(wèn)號(hào) ?,
* 并為每個(gè)占位符構(gòu)建相應(yīng)的 ParameterMapping
*/
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// 調(diào)用 StaticSqlSource 的 getBoundSql 獲取 BoundSql
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 將 DynamicContext 的 ContextMap 中的內(nèi)容拷貝到 BoundSql 中
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
該方法由數(shù)個(gè)步驟組成其骄,這里總結(jié)一下:
- 1亏镰、創(chuàng)建 DynamicContext
- 2、解析 SQL 片段拯爽,并將解析結(jié)果存儲(chǔ)到 DynamicContext 中
- 3拆挥、解析 SQL 語(yǔ)句,并構(gòu)建 StaticSqlSource
- 4、調(diào)用 StaticSqlSource 的 getBoundSql 獲取 BoundSql
- 5纸兔、將 DynamicContext 的 ContextMap 中的內(nèi)容拷貝到 BoundSql
DynamicContext
DynamicContext 是 SQL 語(yǔ)句構(gòu)建的上下文,每個(gè) SQL 片段解析完成后否副,都會(huì)將解析結(jié)果存入 DynamicContext 中汉矿。待所有的 SQL 片段解析完畢后,一條完整的 SQL 語(yǔ)句就會(huì)出現(xiàn)在 DynamicContext 對(duì)象中备禀。
public class DynamicContext {
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
//bindings 則用于存儲(chǔ)一些額外的信息洲拇,比如運(yùn)行時(shí)參數(shù)
private final ContextMap bindings;
//sqlBuilder 變量用于存放 SQL 片段的解析結(jié)果
private final StringBuilder sqlBuilder = new StringBuilder();
public DynamicContext(Configuration configuration, Object parameterObject) {
// 創(chuàng)建 ContextMap,并將運(yùn)行時(shí)參數(shù)放入ContextMap中
if (parameterObject != null && !(parameterObject instanceof Map)) {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
// 存放運(yùn)行時(shí)參數(shù) 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
// 從運(yùn)行時(shí)參數(shù)中查找結(jié)果曲尸,這里會(huì)在${name}解析時(shí)赋续,通過(guò)name獲取運(yùn)行時(shí)參數(shù)值,替換掉${name}字符串
return parameterMetaObject.getValue(strKey);
}
return null;
}
}
// 省略部分代碼
}
解析 SQL 片段
接著我們來(lái)看看解析SQL片段的邏輯
rootSqlNode.apply(context);
對(duì)于一個(gè)包含了 ${} 占位符另患,或 <if>纽乱、<where> 等標(biāo)簽的 SQL,在解析的過(guò)程中昆箕,會(huì)被分解成多個(gè)片段鸦列。每個(gè)片段都有對(duì)應(yīng)的類型,每種類型的片段都有不同的解析邏輯鹏倘。在源碼中薯嗤,片段這個(gè)概念等價(jià)于 sql 節(jié)點(diǎn),即 SqlNode纤泵。
StaticTextSqlNode 用于存儲(chǔ)靜態(tài)文本骆姐,TextSqlNode 用于存儲(chǔ)帶有 ${} 占位符的文本,IfSqlNode 則用于存儲(chǔ) <if> 節(jié)點(diǎn)的內(nèi)容捏题。MixedSqlNode 內(nèi)部維護(hù)了一個(gè) SqlNode 集合玻褪,用于存儲(chǔ)各種各樣的 SqlNode。接下來(lái)涉馅,我將會(huì)對(duì) MixedSqlNode 归园、StaticTextSqlNode、TextSqlNode稚矿、IfSqlNode庸诱、WhereSqlNode 以及 TrimSqlNode 等進(jìn)行分析
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) {
// 調(diào)用 salNode 對(duì)象本身的 apply 方法解析 sql
sqlNode.apply(context);
}
return true;
}
}
MixedSqlNode 可以看做是 SqlNode 實(shí)現(xiàn)類對(duì)象的容器,凡是實(shí)現(xiàn)了 SqlNode 接口的類都可以存儲(chǔ)到 MixedSqlNode 中晤揣,包括它自己桥爽。MixedSqlNode 解析方法 apply 邏輯比較簡(jiǎn)單,即遍歷 SqlNode 集合昧识,并調(diào)用其他 SqlNode實(shí)現(xiàn)類對(duì)象的 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) {
//直接拼接當(dāng)前sql片段的文本到DynamicContext的sqlBuilder中
context.appendSql(text);
return true;
}
}
StaticTextSqlNode 用于存儲(chǔ)靜態(tài)文本,直接將其存儲(chǔ)的 SQL 的文本值拼接到 DynamicContext 的sqlBuilder中即可。下面分析一下 TextSqlNode缀去。
TextSqlNode
public class TextSqlNode implements SqlNode {
private final String text;
private final Pattern injectionFilter;
@Override
public boolean apply(DynamicContext context) {
// 創(chuàng)建 ${} 占位符解析器
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
// 解析 ${} 占位符侣灶,通過(guò)ONGL 從用戶傳入的參數(shù)中獲取結(jié)果,替換text中的${} 占位符
// 并將解析結(jié)果的文本拼接到DynamicContext的sqlBuilder中
context.appendSql(parser.parse(text));
return true;
}
private GenericTokenParser createParser(TokenHandler handler) {
// 創(chuàng)建占位符解析器
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);
}
// 通過(guò) ONGL 從用戶傳入的參數(shù)中獲取結(jié)果
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
// 通過(guò)正則表達(dá)式檢測(cè) srtValue 有效性
checkInjection(srtValue);
return srtValue;
}
}
}
GenericTokenParser 是一個(gè)通用的標(biāo)記解析器缕碎,用于解析形如 {name}的形式咏雌,從運(yùn)行時(shí)參數(shù)的Map中獲取到key為name的值凡怎,直接用運(yùn)行時(shí)參數(shù)替換掉 ${name}字符串,將替換后的text字符串拼接到DynamicContext的sqlBuilder中
舉個(gè)例子吧赊抖,比喻我們有如下SQL
SELECT * FROM user WHERE name = '${name}' and id= ${id}
假如我們傳的參數(shù) Map中name值為 chenhao,id為1统倒,那么該 SQL 最終會(huì)被解析成如下的結(jié)果:
SELECT * FROM user WHERE name = 'chenhao'; DROP TABLE user;#'
由于傳入的參數(shù)沒(méi)有經(jīng)過(guò)轉(zhuǎn)義,最終導(dǎo)致了一條 SQL 被惡意參數(shù)拼接成了兩條 SQL氛雪。這就是為什么我們不應(yīng)該在 SQL 語(yǔ)句中是用 ${} 占位符房匆,風(fēng)險(xiǎn)太大。接著我們來(lái)看看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) {
// 通過(guò) ONGL 評(píng)估 test 表達(dá)式的結(jié)果
if (evaluator.evaluateBoolean(test, context.getBindings())) {
// 若 test 表達(dá)式中的條件成立注暗,則調(diào)用其子節(jié)點(diǎn)節(jié)點(diǎn)的 apply 方法進(jìn)行解析
// 如果是靜態(tài)SQL節(jié)點(diǎn)坛缕,則會(huì)直接拼接到DynamicContext中
contents.apply(context);
return true;
}
return false;
}
}
IfSqlNode 對(duì)應(yīng)的是 <if test='xxx'> 節(jié)點(diǎn),首先是通過(guò) ONGL 檢測(cè) test 表達(dá)式是否為 true捆昏,如果為 true赚楚,則調(diào)用其子節(jié)點(diǎn)的 apply 方法繼續(xù)進(jìn)行解析。如果子節(jié)點(diǎn)是靜態(tài)SQL節(jié)點(diǎn)骗卜,則子節(jié)點(diǎn)的文本值會(huì)直接拼接到DynamicContext中
好了宠页,其他的SqlNode我就不一一分析了,大家有興趣的可以去看看
解析 #{} 占位符
經(jīng)過(guò)前面的解析寇仓,我們已經(jīng)能從 DynamicContext 獲取到完整的 SQL 語(yǔ)句了举户。但這并不意味著解析過(guò)程就結(jié)束了,因?yàn)楫?dāng)前的 SQL 語(yǔ)句中還有一種占位符沒(méi)有處理遍烦,即 #{}俭嘁。與 ${} 占位符的處理方式不同,MyBatis 并不會(huì)直接將 #{} 占位符替換為相應(yīng)的參數(shù)值服猪,而是將其替換成供填?。其解析是在如下代碼中實(shí)現(xiàn)的
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
我們看到將前面解析過(guò)的sql字符串和運(yùn)行時(shí)參數(shù)的Map作為參數(shù)罢猪,我們來(lái)看看parse方法
public class SqlSourceBuilder extends BaseBuilder {
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 創(chuàng)建 #{} 占位符處理器
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// 創(chuàng)建 #{} 占位符解析器
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 解析 #{} 占位符近她,并返回解析結(jié)果字符串
String sql = parser.parse(originalSql);
// 封裝解析結(jié)果到 StaticSqlSource 中,并返回,因?yàn)樗械膭?dòng)態(tài)參數(shù)都已經(jīng)解析了膳帕,可以封裝成一個(gè)靜態(tài)的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 的對(duì)應(yīng)的 ParameterMapping
parameterMappings.add(buildParameterMapping(content));
// 返回 ?
return "?";
}
}
}
我們看到將Sql中的 #{} 占位符替換成"?"粘捎,并且將對(duì)應(yīng)的參數(shù)轉(zhuǎn)化成ParameterMapping 對(duì)象,通過(guò)buildParameterMapping 完成,最后創(chuàng)建一個(gè)StaticSqlSource,將sql字符串和ParameterMappings為參數(shù)傳入攒磨,返回這個(gè)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} 占位符中的內(nèi)容解析成 Map泳桦。
* #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
* 上面占位符中的內(nèi)容最終會(huì)被解析成如下的結(jié)果:
* {
* "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 的元信息對(duì)象
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
/*
* parameterType 是運(yùn)行時(shí)參數(shù)的類型。如果用戶傳入的是單個(gè)參數(shù)娩缰,比如 Employe 對(duì)象蓬痒,此時(shí)
* parameterType 為 Employe.class。如果用戶傳入的多個(gè)參數(shù)漆羔,比如 [id = 1, author = "chenhao"],
* MyBatis 會(huì)使用 ParamMap 封裝這些參數(shù)狱掂,此時(shí) 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 是一個(gè)自定義的類趋惨,
* 比如 Employe鸟顺,此時(shí)為該類創(chuàng)建一個(gè)元信息對(duì)象
*/
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
// 檢測(cè)參數(shù)對(duì)象有沒(méi)有與 property 想對(duì)應(yīng)的 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,則以用戶的配置為準(zhǔn)
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));
}
// 構(gòu)建 ParameterMapping 對(duì)象
return builder.build();
}
}
}
SQL 中的 #{name, ...} 占位符被替換成了問(wèn)號(hào) ?器虾。#{name, ...} 也被解析成了一個(gè) ParameterMapping 對(duì)象讯嫂。我們?cè)賮?lái)看一下 StaticSqlSource 的創(chuàng)建過(guò)程。如下:
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) {
// 創(chuàng)建 BoundSql 對(duì)象
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
最后我們通過(guò)創(chuàng)建的StaticSqlSource就可以獲取BoundSql對(duì)象了兆沙,并傳入運(yùn)行時(shí)參數(shù)
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
也就是調(diào)用上面創(chuàng)建的StaticSqlSource 中的getBoundSql方法欧芽,這是簡(jiǎn)單的 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;
}
//略......
}
我們看到只是做簡(jiǎn)單的賦值葛圃。BoundSql中包含了sql千扔,#{}解析成的parameterMappings,還有運(yùn)行時(shí)參數(shù)parameterObject库正。好了曲楚,SQL解析我們就介紹這么多。我們先回顧一下我們代碼是從哪里開(kāi)始的
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);
// 創(chuàng)建 CacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 調(diào)用重載方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
如上褥符,我們剛才都是分析的第三行代碼龙誊,獲取到了BoundSql,CacheKey 和二級(jí)緩存有關(guān)喷楣,我們留在下一篇文章單獨(dú)來(lái)講趟大,接著我們看第七行重載方法 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();
// 若映射文件中未配置緩存或參照緩存,此時(shí) 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) {
// 若緩存未命中抡蛙,則調(diào)用被裝飾類的 query 方法护昧,也就是SimpleExecutor的query方法
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 調(diào)用被裝飾類的 query 方法,這里的delegate我們知道應(yīng)該是SimpleExecutor
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
上面的代碼涉及到了二級(jí)緩存,若二級(jí)緩存為空粗截,或未命中惋耙,則調(diào)用被裝飾類的 query 方法。被裝飾類為SimpleExecutor,而SimpleExecutor繼承BaseExecutor绽榛,那我們來(lái)看看 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++;
// 從一級(jí)緩存中獲取緩存項(xiàng)湿酸,一級(jí)緩存我們也下一篇文章單獨(dú)講
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 一級(jí)緩存未命中,則從數(shù)據(jù)庫(kù)中查詢
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;
}
}
從一級(jí)緩存中查找查詢結(jié)果灭美。若緩存未命中推溃,再向數(shù)據(jù)庫(kù)進(jìn)行查詢。至此我們明白了一級(jí)二級(jí)緩存的大概思路届腐,先從二級(jí)緩存中查找铁坎,若未命中二級(jí)緩存,再?gòu)囊患?jí)緩存中查找犁苏,若未命中一級(jí)緩存硬萍,再?gòu)臄?shù)據(jù)庫(kù)查詢數(shù)據(jù),那我們來(lái)看看是怎么從數(shù)據(jù)庫(kù)查詢的
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;
// 向緩存中存儲(chǔ)一個(gè)占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 調(diào)用 doQuery 進(jìn)行查詢
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除占位符
localCache.removeObject(key);
}
// 緩存查詢結(jié)果
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
}
調(diào)用了doQuery方法進(jìn)行查詢围详,最后將查詢結(jié)果放入一級(jí)緩存朴乖,我們來(lái)看看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();
// 創(chuàng)建 StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 創(chuàng)建 Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 執(zhí)行查詢操作
return handler.<E>query(stmt, resultHandler);
} finally {
// 關(guān)閉 Statement
closeStatement(stmt);
}
}
}
我們先來(lái)看看第一步創(chuàng)建StatementHandler
創(chuàng)建StatementHandler
StatementHandler有什么作用呢?通過(guò)這個(gè)對(duì)象獲取Statement對(duì)象助赞,然后填充運(yùn)行時(shí)參數(shù)买羞,最后調(diào)用query完成查詢。我們來(lái)看看其創(chuàng)建過(guò)程
public class Configuration {
protected final InterceptorChain interceptorChain = new InterceptorChain();
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 創(chuàng)建具有路由功能的 StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 應(yīng)用插件到 StatementHandler 上
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
}
我們看看RoutingStatementHandler的構(gòu)造方法
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根據(jù) StatementType 創(chuàng)建不同的 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 的構(gòu)造方法會(huì)根據(jù) MappedStatement 中的 statementType 變量創(chuàng)建不同的 StatementHandler 實(shí)現(xiàn)類雹食。那statementType 是什么呢畜普?我們還要回顧一下MappedStatement 的創(chuàng)建過(guò)程
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 的默認(rèn)類型為PREPARED,這里將會(huì)創(chuàng)建PreparedStatementHandler婉徘。
接著我們看下面一行代碼prepareStatement,
創(chuàng)建 Statement
創(chuàng)建 Statement 在 stmt = prepareStatement(handler, ms.getStatementLog());
這句代碼漠嵌,那我們跟進(jìn)去看看
public class SimpleExecutor extends BaseExecutor {
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 獲取數(shù)據(jù)庫(kù)連接
Connection connection = getConnection(statementLog);
// 創(chuàng)建 Statement,
stmt = handler.prepare(connection, transaction.getTimeout());
// 為 Statement 設(shè)置參數(shù)
handler.parameterize(stmt);
return stmt;
}
}
在上面的代碼中我們終于看到了和jdbc相關(guān)的內(nèi)容了盖呼,創(chuàng)建完Statement儒鹿,最后就可以執(zhí)行查詢操作了。由于篇幅的原因几晤,我們留在下一篇文章再來(lái)詳細(xì)講解