動態(tài)Sql介紹
動態(tài) SQL 是 MyBatis 的強(qiáng)大特性之一。如果你使用過 JDBC 或其它類似的框架,你應(yīng)該能理解根據(jù)不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態(tài) SQL,可以徹底擺脫這種痛苦。
使用動態(tài) SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強(qiáng)大的動態(tài) SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
Mybatis動態(tài)解析里面有2個核心的類SqlNode、SqlSource、ExpressionEvaluator。Mybatis動態(tài)Sql使用分為2個部分:動態(tài)Sql解析、動態(tài)Sql拼接執(zhí)行。
封裝SqlNode
SqlNode是在解析Xml文件的時候?qū)討B(tài)Sql進(jìn)行解析,并存在MappedStatement的sqlSource屬性中。對于嵌套動態(tài)Sql,mybatis用遞歸調(diào)用來進(jìn)行解析。這塊東西個人覺得還是比較繞,所以這塊博主準(zhǔn)備事例、源碼、執(zhí)行結(jié)果一起講解。
Sql腳本分類
在Mybatis中Sql腳本分為2種類型:靜態(tài)Sql和動態(tài)Sql。下面我們通過具體的源碼來看下2者區(qū)分。
靜態(tài)Sql和動態(tài)Sql
靜態(tài)Sql說白了就沒有太任何判斷了解的Sql腳本。
//Select是查詢的一些屬性
SqlNode類結(jié)果體系

看mybatis代碼很多時候可以看到這種結(jié)構(gòu)。每個SqlNode負(fù)責(zé)自己那塊功能。職責(zé)單一。SqlNode的核心方法apply就是通過ExpressionEvaluator來解析OGNL表達(dá)式數(shù)據(jù)的。接下來我們看看Mybatis是如何遞歸解析動態(tài)sql腳本的。
//解析Sql腳本節(jié)點(diǎn) publicSqlSourceparseScriptNode(){ //解析靜態(tài)和動態(tài)腳本,并存在MixedSqlNode里面 //這行代碼很關(guān)鍵,后面我們會去分析parseDynamicTags這里就是一層一層遞歸調(diào)用該方法把Sql腳本生成MixedSqlNode對象。 MixedSqlNoderootSqlNode=parseDynamicTags(context); SqlSourcesqlSource=null; //是否為動態(tài)Sql if(isDynamic){ //動態(tài)Sql則生成DynamicSqlSource sqlSource=newDynamicSqlSource(configuration,rootSqlNode); }else{ //否則為靜態(tài)SqlSource sqlSource=newRawSqlSource(configuration,rootSqlNode,parameterType); } returnsqlSource; }
//Anhighlightedblock
protectedMixedSqlNodeparseDynamicTags(XNodenode){
//創(chuàng)建個SqlNode,這個列表存了當(dāng)前Sql腳本節(jié)點(diǎn)下的所有的SqlNode信息
Listcontents=newArrayList();
NodeListchildren=node.getNode().getChildNodes();
for(inti=0;iinSQLstatement.");
}
//調(diào)用對應(yīng)的handler進(jìn)行節(jié)點(diǎn)處理,遞歸調(diào)用就在這塊
handler.handleNode(child,contents);
isDynamic=true;
}
}
//創(chuàng)建MixedSqlNode
returnnewMixedSqlNode(contents);
}
//下面我們看下IfHandler是如何處理,IfHandler是XMLScriptBuilder的內(nèi)部類
privateclassIfHandlerimplementsNodeHandler{
publicIfHandler(){
//PreventSyntheticAccess
}
//我們著重分析這個方法
@Override
publicvoidhandleNode(XNodenodeToHandle,ListtargetContents){
//調(diào)用parseDynamicTags進(jìn)行節(jié)點(diǎn)解析。這里就是遞歸,又調(diào)用了上面的方法。
MixedSqlNodemixedSqlNode=parseDynamicTags(nodeToHandle);
//獲取if對應(yīng)的表達(dá)式
Stringtest=nodeToHandle.getStringAttribute("test");
//創(chuàng)建IfSqlNode
IfSqlNodeifSqlNode=newIfSqlNode(mixedSqlNode,test);
targetContents.add(ifSqlNode);
}
}
下面我們根據(jù)Sql腳本和執(zhí)行結(jié)果來分析。
//靜態(tài)Sql腳本和嵌套的動態(tài)Sql腳本
下面我們分析下執(zhí)行結(jié)果:

上面遞歸結(jié)果已經(jīng)用不通顏色標(biāo)記了,大家自己看下。特別需要看下IfSqlNode的屬性。
動態(tài)Sql解析
動態(tài)Sql解析主要是執(zhí)行數(shù)據(jù)庫操作的時候把動態(tài)Sql轉(zhuǎn)換成JDBC能識別的Sql腳本。Mybatis中主要是通過SqlSource來解析Sql腳本,替換成JDBC能識別的Sql腳本。我們先看下類圖。

SqlSource:提供了Sql解析的行為。
RawSqlSource:靜態(tài)Sql腳本的編譯,只生成一次StaticSqlSource。
DynamicSqlSource:每次調(diào)用都會生成StaticSqlSource。每次調(diào)用傳入?yún)?shù)可能不一樣。需要每次生成StaticSqlSource。
ProviderSqlSource:第三方腳本語言的集成。
FreeMarkerSqlSource:對FreeMarker的支持。
StaticSqlSource:StaticSqlSource只是對上面4中類型做了層封裝。博主沒有這個類會更清爽些。
我們這次主要對StaticSqlSource、RawSqlSource、和DynamicSqlSource進(jìn)行分析。
StaticSqlSource
其實StaticSqlSource就是對其他幾種類型Sql處理器結(jié)果進(jìn)行包裝。我們看下源碼。
//我們主要分析下getBoundSql publicclassStaticSqlSourceimplementsSqlSource{ privatefinalStringsql; privatefinalListparameterMappings; privatefinalConfigurationconfiguration; publicStaticSqlSource(Configurationconfiguration,Stringsql){ this(configuration,sql,null); } publicStaticSqlSource(Configurationconfiguration,Stringsql,List parameterMappings){ this.sql=sql; this.parameterMappings=parameterMappings; this.configuration=configuration; } //getBoundSql就是創(chuàng)建一個BoundSql對象。 @Override publicBoundSqlgetBoundSql(ObjectparameterObject){ returnnewBoundSql(configuration,sql,parameterMappings,parameterObject); } }
看完是不是非常簡單,其實有些代碼確實沒有我們想象中那么難。
RawSqlSource
//我們著重分析RawSqlSource方法
publicclassRawSqlSourceimplementsSqlSource{
privatefinalSqlSourcesqlSource;
publicRawSqlSource(Configurationconfiguration,SqlNoderootSqlNode,Class>parameterType){
this(configuration,getSql(configuration,rootSqlNode),parameterType);
}
//這里實現(xiàn)了對靜態(tài)腳本的解析,所謂的靜態(tài)腳本解析就是把#{}解析成?靜態(tài)Sql解析是在解析Mapper.xml的時候執(zhí)行的
publicRawSqlSource(Configurationconfiguration,Stringsql,Class>parameterType){
SqlSourceBuildersqlSourceParser=newSqlSourceBuilder(configuration);
Class>clazz=parameterType==null?Object.class:parameterType;
//通過調(diào)用SqlSourceBuilder的parse方法來解析Sql
sqlSource=sqlSourceParser.parse(sql,clazz,newHashMap());
}
privatestaticStringgetSql(Configurationconfiguration,SqlNoderootSqlNode){
DynamicContextcontext=newDynamicContext(configuration,null);
rootSqlNode.apply(context);
returncontext.getSql();
}
@Override
publicBoundSqlgetBoundSql(ObjectparameterObject){
returnsqlSource.getBoundSql(parameterObject);
}
}
下面我們來看下SqlSourceBuilder的parse方法
publicSqlSourceparse(StringoriginalSql,Class>parameterType,MapadditionalParameters){ ParameterMappingTokenHandlerhandler=newParameterMappingTokenHandler(configuration,parameterType,additionalParameters); //找到Sql腳本中#{}符號的腳本用?號進(jìn)行替代。GenericTokenParser里面代碼比較復(fù)雜,博主也沒有研究。 //有興趣自己可以研究下。 GenericTokenParserparser=newGenericTokenParser("#{","}",handler); Stringsql=parser.parse(originalSql); returnnewStaticSqlSource(configuration,sql,handler.getParameterMappings()); }
DynamicSqlSource
動態(tài)Sql解析主要由DynamicSqlSource來完成。這里面又是通過遞歸調(diào)進(jìn)行sql解析。我們還是延用上面的Sql給大家講解。
publicclassDynamicSqlSourceimplementsSqlSource{
privatefinalConfigurationconfiguration;
privatefinalSqlNoderootSqlNode;
publicDynamicSqlSource(Configurationconfiguration,SqlNoderootSqlNode){
this.configuration=configuration;
this.rootSqlNode=rootSqlNode;
}
@Override
publicBoundSqlgetBoundSql(ObjectparameterObject){
//動態(tài)Sql解析上下文
DynamicContextcontext=newDynamicContext(configuration,parameterObject);
//rootSqlNode就是我們前面講解的,把動態(tài)Sql解析成SqlNode對象。外層為MixedSqlNode節(jié)點(diǎn),節(jié)點(diǎn)存儲了
//節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。里面遞歸調(diào)用并根據(jù)傳入?yún)?shù)的屬性檢查是否需要拼接sql
rootSqlNode.apply(context);
//這塊代碼和上面靜態(tài)Sql接代碼一致。
SqlSourceBuildersqlSourceParser=newSqlSourceBuilder(configuration);
Class>parameterType=parameterObject==null?Object.class:parameterObject.getClass();
//把我們動態(tài)Sql中的#{}替換成?
SqlSourcesqlSource=sqlSourceParser.parse(context.getSql(),parameterType,context.getBindings());
BoundSqlboundSql=sqlSource.getBoundSql(parameterObject);
for(Map.Entryentry:context.getBindings().entrySet()){
boundSql.setAdditionalParameter(entry.getKey(),entry.getValue());
}
returnboundSql;
}
}
動態(tài)Sql解析apply方法博主只根據(jù)場景介紹下MixedSqlNode和IfSqlNode的apply方法。其他有興趣自己去研究下。邏輯大體一致,實現(xiàn)有些區(qū)別。
publicclassMixedSqlNodeimplementsSqlNode{
privatefinalListcontents;
publicMixedSqlNode(Listcontents){
this.contents=contents;
}
//獲取循環(huán)SqlNode列表的所有SqlNode,調(diào)用apply方法根據(jù)傳入?yún)?shù)和條件進(jìn)行靜態(tài)sql的拼接。
//列表中的SqlNode可能是一個簡單的SqlNode對象,也可能是一個MixedSqlNode或者有更多的嵌套。
//博主的例子就是3個嵌套If查詢。根據(jù)博主的Sql腳本,這里直接會調(diào)用IfSqlNode的apply方法。
//我們接下來看下IfSqlNode是如何實現(xiàn)的。
@Override
publicbooleanapply(DynamicContextcontext){
for(SqlNodesqlNode:contents){
sqlNode.apply(context);
}
returntrue;
}
}
IfSqlNode的apply
publicclassIfSqlNodeimplementsSqlNode{
//ExpressionEvaluator會調(diào)用ognl來對表達(dá)式進(jìn)行解析
privatefinalExpressionEvaluatorevaluator;
privatefinalStringtest;
privatefinalSqlNodecontents;
publicIfSqlNode(SqlNodecontents,Stringtest){
this.test=test;
this.contents=contents;
this.evaluator=newExpressionEvaluator();
}
@Override
publicbooleanapply(DynamicContextcontext){
//context.getBindings()里面就存儲這請求參數(shù),這里是一個HashMap,OGNl里面代碼博主沒有研究。
//如果條件if成立,直接獲取contents中的SqlNode的apply方法進(jìn)行動態(tài)腳本處理。
if(evaluator.evaluateBoolean(test,context.getBindings())){
contents.apply(context);
returntrue;
}
returnfalse;
}
}
這塊代碼很多遞歸調(diào)用,博主自認(rèn)為講的不太透徹,所以大家看完務(wù)必自己去調(diào)試下。
總結(jié)
Mybatis動態(tài)Sql從解析到執(zhí)行分為2個過程下面對這個2個過程進(jìn)行簡單總結(jié)。
1.動態(tài)Sql生成SqlNode信息,這個過程發(fā)生在對select、update等Sql語句解析過程。如果是靜態(tài)Sql直接會把#{}替換成?。
2.動態(tài)Sql解析在獲取BoundSql時候觸發(fā)。會調(diào)用SqlNode的apply進(jìn)行Sql解析成靜態(tài)Sql,然后把#{}替換成?,并綁定ParameterMapping映射。
-
SQL
+關(guān)注
關(guān)注
1文章
789瀏覽量
46157 -
源碼
+關(guān)注
關(guān)注
8文章
678瀏覽量
30914 -
腳本
+關(guān)注
關(guān)注
1文章
405瀏覽量
28993
原文標(biāo)題:Mybatis動態(tài)Sql處理
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
在Delphi中動態(tài)地使用SQL查詢語句
DRDS分布式SQL引擎—執(zhí)行計劃介紹
為什么要動態(tài)sql語句?
基于數(shù)據(jù)窗口對象SQL 語法的動態(tài)移植技術(shù)探討
Delphi教程之在SQL查詢中使用動態(tài)參數(shù)
SQL教程之什么是SQL能做什么SQL基礎(chǔ)的詳細(xì)資料介紹
java的動態(tài)SQL詳細(xì)資料說明
SQL注入到Getshell的教程
一文掌握MyBatis的動態(tài)SQL使用與原理
PROC SQL介紹

動態(tài)Sql介紹
評論