Mybatis3.5.1源碼分析
- Mybatis-SqlSessionFactoryBuilder淑掌,XMLConfigBuilder,XPathParser源碼解析
- Mybatis-Configuration源碼解析
- Mybatis-事務(wù)對(duì)象源碼解析
- Mybatis-數(shù)據(jù)源源碼解析
- Mybatis緩存策略源碼解析
- Mybatis-DatabaseIdProvider源碼解析
- Mybatis-TypeHandler源碼解析
- Mybatis-Reflector源碼解析
- Mybatis-ObjectFactory达椰,ObjectWrapperFactory源碼分析
- Mybatis-Mapper各類標(biāo)簽封裝類源碼解析
- Mybatis-XMLMapperBuilder,XMLStatmentBuilder源碼分析
- Mybatis-MapperAnnotationBuilder源碼分析
- [Mybatis-MetaObject,MetaClass源碼解析]http://www.reibang.com/p/f51fa552f30a)
- Mybatis-LanguageDriver源碼解析
- Mybatis-SqlSource源碼解析
- Mybatis-SqlNode源碼解析
- Mybatis-KeyGenerator源碼解析
- Mybatis-Executor源碼解析
- Mybatis-ParameterHandler源碼解析
- Mybatis-StatementHandler源碼解析
- Mybatis-DefaultResultSetHandler(一)源碼解析
- Mybatis-DefaultResultSetHandler(二)源碼解析
- Mybatis-ResultHandler,Cursor,RowBounds 源碼分析
- Mybatis-MapperProxy源碼解析
- Mybatis-SqlSession源碼解析
- Mybatis-Interceptor源碼解析
SqlNode
作用于SqlSource
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
/**
* <p>
* 各個(gè)類型的動(dòng)態(tài)SQL標(biāo)簽的業(yè)務(wù)實(shí)現(xiàn)邏輯都會(huì)在該接口上實(shí)現(xiàn)
* </p>
* <p>
* 參考博客:
* <ol>
* <li><a >https://blog.csdn.net/wp120453/article/details/93736335</a></li>
* <li><a href='http://www.reibang.com/p/68f6bb7febd8'>http://www.reibang.com/p/68f6bb7febd8</a></li>
* </ol>
* </p>
* @author Clinton Begin
*/
public interface SqlNode {
/**
* 將解析出來(lái)的值應(yīng)用到context中
* @param context 一個(gè)用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
* @return 是否應(yīng)用到 {@code context} 中
*/
boolean apply(DynamicContext context);
}
DynamicContext
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
import ognl.OgnlContext;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
/**
* 主要用于記錄解析動(dòng)態(tài)SQL語(yǔ)句之后產(chǎn)生的SQL語(yǔ)句片段,可以認(rèn)為它是一個(gè)用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
* <ol>
* <li>設(shè)置 OGNL的屬性訪問(wèn)器项乒,實(shí)現(xiàn)ContextMap和ContextAccessor</li>
* <li>用StringJoiner sqlBuilder = new StringJoiner(" ")記錄Sql啰劲,其實(shí)就是將之前解析的多個(gè)Sql片段進(jìn)行合并。</li>
* <li>在每個(gè)SqlNode實(shí)現(xiàn)類中檀何,會(huì)調(diào)用每個(gè)SqlNode的apply(DynamicContext context)方法蝇裤,將SQL添加到StringJoiner中</li>
* <li>DynamicContext中有一個(gè)內(nèi)部類ContextMap用于記錄傳入的參數(shù)。若傳入的參數(shù)不是Map類型時(shí)频鉴,會(huì)創(chuàng)建對(duì)應(yīng)的MetaObject對(duì)象栓辜,并封裝成ContextMap對(duì)象</li>
* </ol>
* <p>
* 參考博客:<a >https://blog.csdn.net/LHQJ1992/article/details/90320639</a>
* </p>
* @author Clinton Begin
*/
public class DynamicContext {
/**
* ParameterObject的KEY名
*/
public static final String PARAMETER_OBJECT_KEY = "_parameter";
/**
* databaseId的KEY名
*/
public static final String DATABASE_ID_KEY = "_databaseId";
static {
/**
* 注冊(cè)特定PropertyAccessor的方式:OgnlRuntime.setPropertyAccessor(clz,PropertyAccessor)
* clz是Class對(duì)象,它可以是目標(biāo)對(duì)象的具體Class垛孔,也可以是父類藕甩、甚至是目標(biāo)對(duì)象實(shí)現(xiàn)的接口之一。
*/
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}
/**
* 綁定關(guān)系
*/
private final ContextMap bindings;
/**
* SQL腳本
* <p>
* StringJoiner是Java8新出的一個(gè)類周荐,用于構(gòu)造由分隔符分隔的字符序列狭莱,
* 并可選擇性地從提供的前綴開(kāi)始和以提供的后綴結(jié)尾悯姊。省的我們開(kāi)發(fā)人員再次通過(guò)StringBuffer
* 或者StingBuilder拼接。
* </p>
*/
private final StringJoiner sqlBuilder = new StringJoiner(" ");
/**
* 唯一數(shù)字
*/
private int uniqueNumber = 0;
/**
*
* @param configuration mybatis全局配置信息
* @param parameterObject 參數(shù)對(duì)象
*/
public DynamicContext(Configuration configuration, Object parameterObject) {
//參數(shù)對(duì)象是Map時(shí)
if (parameterObject != null && !(parameterObject instanceof Map)) {
//構(gòu)造parameterObject的元對(duì)象
MetaObject metaObject = configuration.newMetaObject(parameterObject);
//是否存在對(duì)parameterObject的TypeHandler
boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
bindings = new ContextMap(metaObject, existsTypeHandler);
} else {
bindings = new ContextMap(null, false);
}
//添加parameteObject和databaseId到bindings
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
/**
*
* @return {@see #bindings}
*/
public Map<String, Object> getBindings() {
return bindings;
}
/**
* 賦值給 {@see #bindings}
* @param name bindings.key
* @param value bindings.value
*/
public void bind(String name, Object value) {
bindings.put(name, value);
}
/**
* 添加SQL腳本到 {@link #sqlBuilder}
* @param sql sql腳本
*/
public void appendSql(String sql) {
sqlBuilder.add(sql);
}
/**
* 獲取SQL腳本,去除了空格
* @return {@link #sqlBuilder}.toString().trim();
*/
public String getSql() {
return sqlBuilder.toString().trim();
}
/**
* 獲取唯一數(shù)字,每獲取一次 {@link #uniqueNumber} +1
* @return {@see #uniqueNumber}
*/
public int getUniqueNumber() {
return uniqueNumber++;
}
/**
* 上下文Map
*/
static class ContextMap extends HashMap<String, Object> {
private static final long serialVersionUID = 2977601501966151582L;
/**
* 參數(shù)元對(duì)象
*/
private final MetaObject parameterMetaObject;
/**
* 應(yīng)變參數(shù)對(duì)象
*/
private final boolean fallbackParameterObject;
/**
*
* @param parameterMetaObject 參數(shù)元對(duì)象
* @param fallbackParameterObject 應(yīng)變參數(shù)對(duì)象
*/
public ContextMap(MetaObject parameterMetaObject, boolean fallbackParameterObject) {
this.parameterMetaObject = parameterMetaObject;
this.fallbackParameterObject = fallbackParameterObject;
}
@Override
public Object get(Object key) {
String strKey = (String) key;
//如果key存在在容器中贩毕,直接返回
if (super.containsKey(strKey)) {
return super.get(strKey);
}
//參數(shù)元對(duì)象為null悯许,直接返回
if (parameterMetaObject == null) {
return null;
}
//是應(yīng)變參數(shù)對(duì)且沒(méi)有在參數(shù)元對(duì)象像中找到對(duì)應(yīng)strKey對(duì)應(yīng)的Getter方法
if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
return parameterMetaObject.getOriginalObject();//返回參數(shù)元對(duì)象的原始對(duì)象
} else {
// issue #61 do not modify the context when reading 讀取時(shí)不要修改上下文
//從參數(shù)元對(duì)象中獲取值,支持strKey='order[0].item[0].name'的獲取
return parameterMetaObject.getValue(strKey);
}
}
}
static class ContextAccessor implements PropertyAccessor {
@Override
public Object getProperty(Map context, Object target, Object name) {
//這里的map其實(shí)就是ContextMap
Map map = (Map) target;
//直接獲取contextMap中的keyvalue或者獲取MetaObject中的getValue
Object result = map.get(name);
if (map.containsKey(name) || result != null) {
return result;
}
//如參數(shù)對(duì)象本身是個(gè)map,直接獲取
Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
if (parameterObject instanceof Map) {
return ((Map)parameterObject).get(name);
}
return null;
}
/**
* 設(shè)置屬性
* @param context
* @param target 其實(shí)就是ContextMap實(shí)例
* @param name 傳給ContextMap實(shí)例的key
* @param value 傳給ContextMap實(shí)例的value
*/
@Override
public void setProperty(Map context, Object target, Object name, Object value) {
Map<Object, Object> map = (Map<Object, Object>) target;
map.put(name, value);
}
@Override
public String getSourceAccessor(OgnlContext arg0, Object arg1, Object arg2) {
return null;
}
@Override
public String getSourceSetter(OgnlContext arg0, Object arg1, Object arg2) {
return null;
}
}
}
ChooseSqlNode
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.List;
/**
* chooseSQL節(jié)點(diǎn)辉阶。
* @author Clinton Begin
*/
public class ChooseSqlNode implements SqlNode {
/**
* otherwise標(biāo)簽
*/
private final SqlNode defaultSqlNode;
/**
* when標(biāo)簽集合
*/
private final List<SqlNode> ifSqlNodes;
/**
*
* @param ifSqlNodes when標(biāo)簽集合
* @param defaultSqlNode otherwise標(biāo)簽
*/
public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
@Override
public boolean apply(DynamicContext context) {
// when 節(jié)點(diǎn)根據(jù) test 表達(dá)式判斷是否生效
for (SqlNode sqlNode : ifSqlNodes) {
if (sqlNode.apply(context)) {
return true;
}
}
// when 節(jié)點(diǎn)如果都未生效先壕,且存在 otherwise 節(jié)點(diǎn),則使用 otherwise 節(jié)點(diǎn)
if (defaultSqlNode != null) {
defaultSqlNode.apply(context);
return true;
}
return false;
}
}
ForEachSqlNode
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.Map;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.session.Configuration;
/**
* ForEach標(biāo)簽
* @author Clinton Begin
*/
public class ForEachSqlNode implements SqlNode {
/**
* foreach的
*/
public static final String ITEM_PREFIX = "__frch_";
private final ExpressionEvaluator evaluator;
/**
* foreach標(biāo)簽的collection屬性谆甜,循環(huán)的集合對(duì)象名
*/
private final String collectionExpression;
/**
* forEach標(biāo)簽下的子節(jié)點(diǎn)垃僚,封裝成MixedSqlNode
*/
private final SqlNode contents;
/**
* foreach標(biāo)簽的open屬性,foreach代碼的開(kāi)始符號(hào)规辱,一般是(和close=")"合用谆棺。常用在in(),values()時(shí)。該參數(shù)可選罕袋。
*/
private final String open;
/**
* foreach標(biāo)簽的close屬性,foreach代碼的關(guān)閉符號(hào)改淑,一般是)和open="("合用。常用在in(),values()時(shí)浴讯。該參數(shù)可選朵夏。
*/
private final String close;
/**
* foreach標(biāo)簽的separator屬性,元素之間的分隔符,例如在in()的時(shí)候榆纽,separator=","會(huì)自動(dòng)在元素中間用“,“隔開(kāi)仰猖,避免手動(dòng)輸入逗號(hào)導(dǎo)致sql錯(cuò)誤,如in(1,2,)這樣奈籽。該參數(shù)可選
*/
private final String separator;
/**
* foreach標(biāo)簽的item屬性饥侵,集合對(duì)象的元素名
*/
private final String item;
/**
* foreach標(biāo)簽的index屬性,在list和數(shù)組中,index是元素的序號(hào)衣屏,在map中躏升,index是元素的key,該參數(shù)可選勾拉。
*/
private final String index;
/**
* mybatis全局配置信息
*/
private final Configuration configuration;
/**
*
* @param configuration mybatis全局配置信息
* @param contents forEach標(biāo)簽下的子節(jié)點(diǎn)煮甥,封裝成MixedSqlNode
* @param collectionExpression foreach標(biāo)簽的collection屬性盗温,循環(huán)的集合對(duì)象名
* @param index foreach標(biāo)簽的index屬性藕赞,在list和數(shù)組中,index是元素的序號(hào),在map中卖局,index是元素的key斧蜕,該參數(shù)可選。
* @param item foreach標(biāo)簽的item屬性砚偶,集合對(duì)象的元素名
* @param open foreach標(biāo)簽的open屬性批销,foreach代碼的開(kāi)始符號(hào)洒闸,一般是(和close=")"合用。常用在in(),values()時(shí)均芽。該參數(shù)可選丘逸。
* @param close foreach標(biāo)簽的close屬性,foreach代碼的關(guān)閉符號(hào),一般是)和open="("合用掀宋。常用在in(),values()時(shí)深纲。該參數(shù)可選。
* @param separator foreach標(biāo)簽的separator屬性,元素之間的分隔符劲妙,例如在in()的時(shí)候湃鹊,separator=","會(huì)自動(dòng)在元素中間用“,“隔開(kāi),避免手動(dòng)輸入逗號(hào)導(dǎo)致sql錯(cuò)誤镣奋,如in(1,2,)這樣币呵。該參數(shù)可選
*/
public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
this.evaluator = new ExpressionEvaluator();
this.collectionExpression = collectionExpression;
this.contents = contents;
this.open = open;
this.close = close;
this.separator = separator;
this.index = index;
this.item = item;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
//獲取參數(shù)對(duì)象
Map<String, Object> bindings = context.getBindings();
//獲取expression對(duì)應(yīng)在parameterObject的對(duì)象的Iterable對(duì)象
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
//沒(méi)有元素就直接返回true
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
//將open添加到context中
applyOpen(context);
// 迭代索引
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
//對(duì)content進(jìn)行裝飾成prefixedContext
if (first || separator == null) {
context = new PrefixedContext(context, "");//首個(gè)元素不需要前綴,所以prefix為空字符串
} else {
context = new PrefixedContext(context, separator);
}
int uniqueNumber = context.getUniqueNumber();
// Issue #709
if (o instanceof Map.Entry) {
// entry 集合項(xiàng)索引為 key侨颈,集合項(xiàng)為 value
@SuppressWarnings("unchecked")
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
applyIndex(context, mapEntry.getKey(), uniqueNumber);
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
// 綁定集合項(xiàng)索引關(guān)系
applyIndex(context, i, uniqueNumber);
// 綁定集合項(xiàng)關(guān)系
applyItem(context, o, uniqueNumber);
}
// 對(duì)解析的表達(dá)式進(jìn)行替換余赢,如 idx = #{index} AND itm = #{item} 替換為 idx = #{__frch_index_1} AND itm = #{__frch_item_1}
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if (first) {
//只要沒(méi)有應(yīng)用前綴,first就一直為true哈垢,正常情況下没佑,這里到這一步,isPrefixApplied一般為false温赔。
first = !((PrefixedContext) context).isPrefixApplied();
}
context = oldContext;
i++;
}
//將close添加到context中
applyClose(context);
//移除item綁定關(guān)系
context.getBindings().remove(item);
//移除index綁定關(guān)系
context.getBindings().remove(index);
return true;
}
/**
* 綁定集合項(xiàng)索引關(guān)系
* @param context PrefixedContext實(shí)例
* @param o index值
* @param i {@code context} 的唯一數(shù)字 context.getUniqueNumber()
*/
private void applyIndex(DynamicContext context, Object o, int i) {
if (index != null) {
context.bind(index, o);
context.bind(itemizeItem(index, i), o);
}
}
/**
* 綁定集合項(xiàng)關(guān)系
* @param context PrefixedContext實(shí)例
* @param o index值
* @param i {@code context} 的唯一數(shù)字 context.getUniqueNumber()
*/
private void applyItem(DynamicContext context, Object o, int i) {
if (item != null) {
context.bind(item, o);
context.bind(itemizeItem(item, i), o);
}
}
/**
* 應(yīng)用 {@link #open}
* @param context 用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
*/
private void applyOpen(DynamicContext context) {
if (open != null) {
context.appendSql(open);
}
}
/**
* 應(yīng)用 {@link #close}
* @param context 用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
*/
private void applyClose(DynamicContext context) {
if (close != null) {
context.appendSql(close);
}
}
/**
* 組裝列表項(xiàng)名
* @param item 項(xiàng)名
* @param i 位置
* @return {@link #ITEM_PREFIX} + item + "_" + i
*/
private static String itemizeItem(String item, int i) {
return ITEM_PREFIX + item + "_" + i;
}
/**
* 對(duì)解析的表達(dá)式進(jìn)行替換的DynamincContext裝飾類
*/
private static class FilteredDynamicContext extends DynamicContext {
/**
* 委托類對(duì)象
*/
private final DynamicContext delegate;
/**
* {@link #delegate} 的 uniqueNumber
*/
private final int index;
/**
* foreach標(biāo)簽的index屬性蛤奢,在list和數(shù)組中,index是元素的序號(hào),
* 在map中陶贼,index是元素的key啤贩,該參數(shù)可選。
*/
private final String itemIndex;
/**
* foreach標(biāo)簽的item屬性拜秧,集合對(duì)象的元素名
*/
private final String item;
/**
*
* @param configuration mybatis全局配置信息
* @param delegate 委托類對(duì)象
* @param itemIndex foreach標(biāo)簽的index屬性痹屹,在list和數(shù)組中,index是元素的序號(hào),在map中枉氮,index是元素的key志衍,該參數(shù)可選。
* @param item foreach標(biāo)簽的item屬性聊替,集合對(duì)象的元素名
* @param i {@code delegate} 的 uniqueNumber
*/
public FilteredDynamicContext(Configuration configuration,DynamicContext delegate, String itemIndex, String item, int i) {
super(configuration, null);
this.delegate = delegate;
this.index = i;
this.itemIndex = itemIndex;
this.item = item;
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public String getSql() {
return delegate.getSql();
}
@Override
public void appendSql(String sql) {
GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
// 對(duì)解析的表達(dá)式進(jìn)行替換楼肪,如 idx = #{index} AND itm = #{item} 替換為 idx = #{__frch_index_1} AND itm = #{__frch_item_1}
String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
if (itemIndex != null && newContent.equals(content)) {
newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
}
return "#{" + newContent + "}";
});
delegate.appendSql(parser.parse(sql));
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
/**
* 帶prefix的記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
*/
private class PrefixedContext extends DynamicContext {
/**
* 委托對(duì)象
*/
private final DynamicContext delegate;
/**
* 前綴
*/
private final String prefix;
/**
* 已經(jīng)應(yīng)用了前綴的標(biāo)記
*/
private boolean prefixApplied;
/**
*
* @param delegate 委托對(duì)象
* @param prefix 前綴
*/
public PrefixedContext(DynamicContext delegate, String prefix) {
super(configuration, null);
this.delegate = delegate;
this.prefix = prefix;
this.prefixApplied = false;
}
/**
* 是否已經(jīng)應(yīng)用了前綴的標(biāo)記
* @return
*/
public boolean isPrefixApplied() {
return prefixApplied;
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public void appendSql(String sql) {
//如果還沒(méi)有添加prefix,且sql經(jīng)過(guò)修剪后長(zhǎng)度不為0惹悄,就會(huì)先添加prefix到delegate
if (!prefixApplied && sql != null && sql.trim().length() > 0) {
delegate.appendSql(prefix);
prefixApplied = true;
}
//添加sql到delegate
delegate.appendSql(sql);
}
@Override
public String getSql() {
return delegate.getSql();
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
}
IfSqlNode
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
/**
* if標(biāo)簽春叫,動(dòng)態(tài) SQL 通常要做的事情是有條件地包含 where 子句的一部分
* @author Clinton Begin
*/
public class IfSqlNode implements SqlNode {
/**
* OGNL 表達(dá)式計(jì)算工具
*/
private final ExpressionEvaluator evaluator;
/**
* ognl表達(dá)式
*/
private final String test;
/**
* if標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
*/
private final SqlNode contents;
/**
*
* @param contents if標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
* @param test ognl表達(dá)式
*/
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// 根據(jù) test 表達(dá)式判斷當(dāng)前節(jié)點(diǎn)是否生效
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
MixedSqlNode
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.List;
/**
* 混合的SQL節(jié)點(diǎn)暂殖,里面存儲(chǔ)著sqlNode結(jié)合价匠,當(dāng)調(diào)用apply方法時(shí),會(huì)遍歷其sqlNode集合的元素呛每,調(diào)用元素的apply進(jìn)行處理踩窖。
* @author Clinton Begin
*/
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
TrimSqlNode
/**
* Copyright 2009-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.ibatis.session.Configuration;
/**
* Trim標(biāo)簽
* @author Clinton Begin
*/
public class TrimSqlNode implements SqlNode {
/**
* trim標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
*/
private final SqlNode contents;
/**
* trim標(biāo)簽的prefix屬性晨横,給sql語(yǔ)句拼接的前綴
*/
private final String prefix;
/**
* trim標(biāo)簽的suffix屬性毙石,給sql語(yǔ)句拼接的后綴
*/
private final String suffix;
/**
* trim標(biāo)簽的prefixOverrides屬性
* <p>
* 去除sql語(yǔ)句前面的關(guān)鍵字或者字符,該關(guān)鍵字或者字符由prefixOverrides屬性指定颓遏,
* 假設(shè)該屬性指定為"AND"徐矩,當(dāng)sql語(yǔ)句的開(kāi)頭為"AND",trim標(biāo)簽將會(huì)去除該"AND"
* </p>
*/
private final List<String> prefixesToOverride;
/**
* trim標(biāo)簽的suffixOverride,去除sql語(yǔ)句后面的關(guān)鍵字或者字符叁幢,該關(guān)鍵字或者字符由suffixOverrides屬性指定
*/
private final List<String> suffixesToOverride;
/**
* mybatis的全局配置信息
*/
private final Configuration configuration;
/**
*
* @param configuration mybatis的全局配置信息
* @param contents trim標(biāo)簽下的字節(jié)點(diǎn)滤灯,封裝成MixedSqlNode
* @param prefix trim標(biāo)簽的prefix屬性
* @param prefixesToOverride trim標(biāo)簽的prefixOverrides屬性
* @param suffix trim標(biāo)簽的suffix屬性
* @param suffixesToOverride trim標(biāo)簽的suffixOverride
*/
public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
}
/**
*
* @param configuration mybatis的全局配置信息
* @param contents trim標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
* @param prefix trim標(biāo)簽的prefix屬性
* @param prefixesToOverride trim標(biāo)簽的prefixOverrides屬性
* @param suffix trim標(biāo)簽的suffix屬性
* @param suffixesToOverride trim標(biāo)簽的suffixOverride
*/
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
/**
* FilteredDynamicContext為DynamincContext的代理類曼玩,提供了根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行
* 對(duì)filteredDynamicContext修剪的方法鳞骤。
*/
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
//將解析出來(lái)的值添加到context中
boolean result = contents.apply(filteredDynamicContext);
//根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行對(duì)filteredDynamicContext修剪
filteredDynamicContext.applyAll();
return result;
}
/**
* 將prefixesToOverride屬性或者suffixesToOverride屬性值根據(jù)'|'分割成List<String>,
* eg:prefixesToOverride='and | or' ==> List{AND,OR}
* @param overrides prefixesToOverride屬性或者suffixesToOverride屬性
* @return 返回根據(jù)'|'分割成List<String>,如果overrides為null黍判,會(huì)返回Collections的靜態(tài)空集合對(duì)象
*/
private static List<String> parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List<String> list = new ArrayList<>(parser.countTokens());
while (parser.hasMoreTokens()) {
//將分割處理的token以大寫(xiě)的形式添加到list中
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
//返回Collections的靜態(tài)空集合對(duì)象
return Collections.emptyList();
}
/**
* DynamincContext的代理類豫尽,提供了根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行
* 對(duì)filteredDynamicContext修剪的方法。
*/
private class FilteredDynamicContext extends DynamicContext {
/**
* DynamicContext委托對(duì)象
*/
private DynamicContext delegate;
/**
* 已經(jīng)應(yīng)用了前綴的標(biāo)記
*/
private boolean prefixApplied;
/**
* 已經(jīng)應(yīng)用了后綴的標(biāo)記
*/
private boolean suffixApplied;
/**
* sql腳本
*/
private StringBuilder sqlBuffer;
/**
*
* @param delegate DynamicContext委托對(duì)象
*/
public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
this.delegate = delegate;
this.prefixApplied = false;
this.suffixApplied = false;
this.sqlBuffer = new StringBuilder();
}
/**
* 對(duì)sqlBuffer根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行修剪
*/
public void applyAll() {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
if (trimmedUppercaseSql.length() > 0) {
applyPrefix(sqlBuffer, trimmedUppercaseSql);
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
delegate.appendSql(sqlBuffer.toString());
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
@Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
@Override
public String getSql() {
return delegate.getSql();
}
/**
* 應(yīng)用前綴
* @param sql sql腳本
* @param trimmedUppercaseSql 去掉了 {@code sql} 的前和尾的所有空格顷帖,以及全部字母轉(zhuǎn)換成了大寫(xiě)的字符串
*/
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!prefixApplied) {
prefixApplied = true;
if (prefixesToOverride != null) {
for (String toRemove : prefixesToOverride) {
//是否以prefiexesToOverride的元素開(kāi)頭美旧,如果是,就以從0位置開(kāi)始刪除贬墩。只會(huì)執(zhí)行一下刪除
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
//插入prefix到sql的開(kāi)頭
if (prefix != null) {
//因?yàn)椴迦氲奈恢檬鞘莝ql的開(kāi)頭榴嗅,所以要先插入空格
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
}
/**
* 應(yīng)用后綴
* @param sql sql腳本
* @param trimmedUppercaseSql 去掉了 {@code sql} 的前和尾的所有空格,以及全部字母轉(zhuǎn)換成了大寫(xiě)的字符串
*/
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if (!suffixApplied) {
suffixApplied = true;
if (suffixesToOverride != null) {
for (String toRemove : suffixesToOverride) {
//刪除在sql中以suffixesToOverride的元素結(jié)尾的字符串陶舞,該刪除只會(huì)執(zhí)行異常
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
break;
}
}
}
//插入suffix到sql的結(jié)尾
if (suffix != null) {
//因?yàn)椴迦氲奈恢檬莝ql的結(jié)尾嗽测,所以要先插入空格
sql.append(" ");
sql.append(suffix);
}
}
}
}
}
SetSqlNode
/**
* Copyright 2009-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.Collections;
import java.util.List;
import org.apache.ibatis.session.Configuration;
/**
* set 元素會(huì)動(dòng)態(tài)前置 SET 關(guān)鍵字,同時(shí)也會(huì)消除無(wú)關(guān)的逗號(hào)肿孵,
* 因?yàn)橛昧藯l件語(yǔ)句之后很可能就會(huì)在生成的賦值語(yǔ)句的后面留下這些逗號(hào)唠粥。
* <p>
* 實(shí)現(xiàn)邏輯是套用TrimSqlNode
* </p>
* @author Clinton Begin
*/
public class SetSqlNode extends TrimSqlNode {
private static final List<String> COMMA = Collections.singletonList(",");
/**
*
* @param configuration mybatis全局配置信息
* @param contents set標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
*/
public SetSqlNode(Configuration configuration,SqlNode contents) {
/**
* 設(shè)置為'SET'為前綴停做,設(shè)置COMMA為trimSqlNode的prefixOverride,到時(shí)候調(diào)用apply
* 方法就會(huì)自動(dòng)添加'SET'作為前綴晤愧,并覆蓋掉'SET'后面COMMA的元素
*/
super(configuration, contents, "SET", COMMA, null, COMMA);
}
}
StaticTextSqlNode
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
/**
* 靜態(tài)文本SQL節(jié)點(diǎn),其apply方法使得只是將存儲(chǔ)的文本內(nèi)容直接添加到DynamicContext對(duì)象中
* @author Clinton Begin
*/
public class StaticTextSqlNode implements SqlNode {
/**
* 靜態(tài)文本
*/
private final String text;
/**
*
* @param text 靜態(tài)文本
*/
public StaticTextSqlNode(String text) {
this.text = text;
}
/**
* 將{@link #text} 直接添加到 {@code context}
* @param context 一個(gè)用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
* @return 寫(xiě)死返回true
*/
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
TextSqlNode
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.regex.Pattern;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.scripting.ScriptingException;
import org.apache.ibatis.type.SimpleTypeRegistry;
/**
* 文本SQL節(jié)點(diǎn)雅宾。用傳入的實(shí)際參數(shù)對(duì)象中的屬性值對(duì)${}進(jìn)行直接替換养涮,并且不會(huì)進(jìn)行任何檢查!
* @author Clinton Begin
*/
public class TextSqlNode implements SqlNode {
/**
* 文本內(nèi)容
*/
private final String text;
/**
* 需要匹配的正則表達(dá)式
*/
private final Pattern injectionFilter;
/**
*
* @param text 文本內(nèi)容
*/
public TextSqlNode(String text) {
this(text, null);
}
/**
*
* @param text 文本內(nèi)容
* @param injectionFilter 需要匹配的正則表達(dá)式
*/
public TextSqlNode(String text, Pattern injectionFilter) {
this.text = text;
this.injectionFilter = injectionFilter;
}
/**
* 是否動(dòng)態(tài)SQL眉抬。當(dāng)sql中包含有${}時(shí)贯吓,就認(rèn)為是動(dòng)態(tài)SQL
*/
public boolean isDynamic() {
//DynamicCheckerTokenParse:當(dāng)sql中包含有${}時(shí),就認(rèn)為是動(dòng)態(tài)SQL
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
//GenericTokenParser會(huì)將檢查到的'${XXX}'的XXX傳遞給DynamicCheckerTokenParser的handleToken方法進(jìn)行處理.
parser.parse(text);
return checker.isDynamic();
}
/**
* 用傳入的實(shí)際參數(shù)對(duì)象中的屬性值對(duì)${}進(jìn)行直接替換蜀变,并且不會(huì)進(jìn)行任何檢查悄谐!
* @param context 用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
* @return 寫(xiě)死了true
*/
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
/**
* GenericTokenParser會(huì)將檢查到的'${XXX}'的XXX傳遞給BindingTokenParser的handleToken方法進(jìn)行處理.
* 將解析處理的結(jié)果添加到context中。
*/
context.appendSql(parser.parse(text));
return true;
}
/**
* 創(chuàng)建通用token解析器
* @param handler token處理器
* @return 專門處理'${...}'形式的token的通用token解析器
*/
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
/**
* 用于獲取替換${}的真實(shí)參數(shù)值
*/
private static class BindingTokenParser implements TokenHandler {
private DynamicContext context;
/**
* 正則表達(dá)式
*/
private Pattern injectionFilter;
public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
}
@Override
public String handleToken(String content) {
//TODO 沒(méi)有看懂這里干嘛的
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);
}
// 從傳入的參數(shù)中獲取到${}對(duì)應(yīng)的值并對(duì)${}進(jìn)行替換
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
//檢查值是否匹配正則表達(dá)式库北,不匹配拋出異常
checkInjection(srtValue);
return srtValue;
}
/**
* 檢查值是否匹配正則表達(dá)式爬舰,不匹配拋出異常
* @param value 值
*/
private void checkInjection(String value) {
if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
}
}
}
/**
* 用于sql的動(dòng)態(tài)檢測(cè),當(dāng)sql中包含有${}時(shí)就認(rèn)為是動(dòng)態(tài)SQL
*/
private static class DynamicCheckerTokenParser implements TokenHandler {
/**
* 是否動(dòng)態(tài)SQL,當(dāng)sql中包含有${}時(shí)就認(rèn)為是動(dòng)態(tài)SQL
*/
private boolean isDynamic;
public DynamicCheckerTokenParser() {
// Prevent Synthetic Access
}
/**
*
* @return {@see #isDynamic}
*/
public boolean isDynamic() {
return isDynamic;
}
/**
* 當(dāng)GenericTokenParser檢測(cè)到sql中包含有${}時(shí)寒瓦,只是簡(jiǎn)單的標(biāo)記為動(dòng)態(tài)
*/
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
}
}
VarDecSqlNode
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
/**
* OGNL SqlNode
* @author Frank D. Martinez [mnesarco]
*/
public class VarDeclSqlNode implements SqlNode {
/**
* 變量名
*/
private final String name;
/**
* 表達(dá)式
*/
private final String expression;
/**
*
* @param var 變量名
* @param exp 表達(dá)式
*/
public VarDeclSqlNode(String var, String exp) {
name = var;
expression = exp;
}
@Override
public boolean apply(DynamicContext context) {
final Object value = OgnlCache.getValue(expression, context.getBindings());
context.bind(name, value);
return true;
}
}
WhereSqlNode
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.xmltags;
import java.util.Arrays;
import java.util.List;
import org.apache.ibatis.session.Configuration;
/**
* where 標(biāo)簽知道只有在一個(gè)以上的if條件有值的情況下才去插入"WHERE"子句情屹。
* 而且,若最后的內(nèi)容是"AND"或"OR"開(kāi)頭的杂腰,where 元素也知道如何將他們?nèi)コ? * <p>
* 實(shí)現(xiàn)邏輯是套用TrimSqlNode
* </p>
* @author Clinton Begin
*/
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");
/**
*
* @param configuration mybatis全局配置信息
* @param contents where標(biāo)簽下的字節(jié)點(diǎn)垃你,封裝成MixedSqlNode
*/
public WhereSqlNode(Configuration configuration, SqlNode contents) {
/**
* 設(shè)置為'WHERE'為前綴低矮,設(shè)置prefixList為trimSqlNode的prefixOverride,到時(shí)候調(diào)用apply
* 方法就會(huì)自動(dòng)添加'WHERE'作為前綴赶促,并覆蓋掉'WHERE'后面prefixList的元素
*/
super(configuration, contents, "WHERE", prefixList, null, null);
}
}