詳解MyBatis的動(dòng)態(tài)SQL實(shí)現(xiàn)原理
前言
MyBatis版本:3.5.6
正文
一. XML文檔中的節(jié)點(diǎn)概念
在分析MyBatis如何支持SQL語(yǔ)句之前,本小節(jié)先分析XML文檔中的節(jié)點(diǎn)概念。XML文檔中的每個(gè)成分都是一個(gè)節(jié)點(diǎn),DOM對(duì)XML節(jié)點(diǎn)的規(guī)定如下所示。
- 整個(gè)文檔是一個(gè)文檔節(jié)點(diǎn);
- 每個(gè)XML標(biāo)簽是一個(gè)元素節(jié)點(diǎn);
- 包含在元素節(jié)點(diǎn)中的文本是文本節(jié)點(diǎn)。
以一個(gè)XML文檔進(jìn)行說明,如下所示。
<provinces> <province name="四川"> <capital>成都</capital> </province> <province name="湖北"> <capital>武漢</capital> </province> </provinces>
如上所示,整個(gè)XML文檔是一個(gè)文檔節(jié)點(diǎn),這個(gè)文檔節(jié)點(diǎn)有一個(gè)子節(jié)點(diǎn),就是<provinces>元素節(jié)點(diǎn),<provinces>元素節(jié)點(diǎn)有五個(gè)子節(jié)點(diǎn),分別是:
- 文本節(jié)點(diǎn);
- <province>元素節(jié)點(diǎn);
- 文本節(jié)點(diǎn),
- <province>元素節(jié)點(diǎn);
- 文本節(jié)點(diǎn)。
注意,在<provinces>元素節(jié)點(diǎn)的子節(jié)點(diǎn)中的文本節(jié)點(diǎn)的文本值均是\n
,表示換行符。
同樣,<province>元素節(jié)點(diǎn)有三個(gè)子節(jié)點(diǎn),分別是:
- 文本節(jié)點(diǎn);
- <capital>元素節(jié)點(diǎn);
- 文本節(jié)點(diǎn)。
這里的文本節(jié)點(diǎn)的文本值也是\n
。
然后<capital>元素節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),為一個(gè)文本節(jié)點(diǎn)。節(jié)點(diǎn)的子節(jié)點(diǎn)之間互為兄弟節(jié)點(diǎn),例如<provinces>元素的五個(gè)子節(jié)點(diǎn)之間互為兄弟節(jié)點(diǎn),name為"四川"的<province>元素節(jié)點(diǎn)的上一個(gè)兄弟節(jié)點(diǎn)為文本節(jié)點(diǎn),下一個(gè)兄弟節(jié)點(diǎn)也為文本節(jié)點(diǎn)。
二. 動(dòng)態(tài)SQL解析流程說明
整體的一個(gè)解析流程如下所示。
也就是寫在映射文件中的一條SQL
,會(huì)最終被解析為DynamicSqlSource
或者RawSqlSource
,前者表示動(dòng)態(tài)SQL
,后者表示靜態(tài)SQL
。
上圖中的MixedSqlNode,其通常的包含關(guān)系可以由下圖定義。
也就是映射文件中定義一條SQL語(yǔ)句的CRUD標(biāo)簽里的各種子元素,均會(huì)被解析為一個(gè)SqlNode,比如包含了${}
的文本,會(huì)被解析為TextSqlNode,不包含${}
的文本,會(huì)被解析為StaticTextSqlNode,<choose>標(biāo)簽會(huì)被解析為ChooseSqlNode等,同時(shí)又因?yàn)?lt;choose>標(biāo)簽中會(huì)再有<when>和<otherwise>子標(biāo)簽,所以ChooseSqlNode中又會(huì)持有這些子標(biāo)簽的SqlNode。
所以一條SQL
最終就是由這條SQL
對(duì)應(yīng)的CRUD
標(biāo)簽解析成的各種SqlNode
組合而成。
三. MyBatis解析動(dòng)態(tài)SQL源碼分析
在詳解MyBatis加載映射文件和動(dòng)態(tài)代理的實(shí)現(xiàn)中已經(jīng)知道,在XMLStatementBuilder的parseStatementNode() 方法中,會(huì)解析映射文件中的<select>,<insert>,<update>和<delete>標(biāo)簽(后續(xù)統(tǒng)一稱為CURD標(biāo)簽),并生成MappedStatement然后緩存到Configuration中。
CURD標(biāo)簽的解析由XMLLanguageDriver完成,每個(gè)標(biāo)簽解析之后會(huì)生成一個(gè)SqlSource,可以理解為SQL語(yǔ)句,本小節(jié)將對(duì)XMLLanguageDriver如何完成CURD標(biāo)簽的解析進(jìn)行討論。
XMLLanguageDriver創(chuàng)建SqlSource的createSqlSource() 方法如下所示。
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder( configuration, script, parameterType); return builder.parseScriptNode(); }
如上所示,createSqlSource() 方法的入?yún)⒅校?strong>XNode就是CURD標(biāo)簽對(duì)應(yīng)的節(jié)點(diǎn),在createSqlSource() 方法中先是創(chuàng)建了一個(gè)XMLScriptBuilder,然后通過XMLScriptBuilder來生成SqlSource。先看一下XMLScriptBuilder的構(gòu)造方法,如下所示。
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; initNodeHandlerMap(); }
在XMLScriptBuilder的構(gòu)造方法中,主要是將CURD標(biāo)簽對(duì)應(yīng)的節(jié)點(diǎn)緩存起來,然后初始化nodeHandlerMap,nodeHandlerMap中存放著處理MyBatis提供的支持動(dòng)態(tài)SQL的標(biāo)簽的處理器,initNodeHandlerMap() 方法如下所示。
private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }
現(xiàn)在分析XMLScriptBuilder的parseScriptNode() 方法,該方法會(huì)創(chuàng)建SqlSource,如下所示。
public SqlSource parseScriptNode() { // 解析動(dòng)態(tài)標(biāo)簽 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { // 創(chuàng)建DynamicSqlSource并返回 sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { // 創(chuàng)建RawSqlSource并返回 sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
在XMLScriptBuilder的parseScriptNode() 方法中,會(huì)根據(jù)XMLScriptBuilder中的isDynamic屬性判斷是創(chuàng)建DynamicSqlSource還是RawSqlSource,在這里暫時(shí)不分析DynamicSqlSource與RawSqlSource的區(qū)別,但是可以推測(cè)在parseDynamicTags() 方法中會(huì)改變isDynamic屬性的值,即在parseDynamicTags() 方法中會(huì)根據(jù)CURD標(biāo)簽的節(jié)點(diǎn)生成一個(gè)MixedSqlNode,同時(shí)還會(huì)改變isDynamic屬性的值以指示當(dāng)前CURD標(biāo)簽中的SQL語(yǔ)句是否是動(dòng)態(tài)的。
MixedSqlNode是什么,isDynamic屬性值在什么情況下會(huì)變?yōu)?strong>true,帶著這些疑問,繼續(xù)看parseDynamicTags() 方法,如下所示。
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); // 獲取節(jié)點(diǎn)的子節(jié)點(diǎn) NodeList children = node.getNode().getChildNodes(); // 遍歷所有子節(jié)點(diǎn) for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // 子節(jié)點(diǎn)為文本節(jié)點(diǎn) String data = child.getStringBody(""); // 基于文本節(jié)點(diǎn)的值并創(chuàng)建TextSqlNode TextSqlNode textSqlNode = new TextSqlNode(data); // isDynamic()方法可以判斷文本節(jié)點(diǎn)值是否有${}占位符 if (textSqlNode.isDynamic()) { // 文本節(jié)點(diǎn)值有${}占位符 // 添加TextSqlNode到集合中 contents.add(textSqlNode); // 設(shè)置isDynamic為true isDynamic = true; } else { // 文本節(jié)點(diǎn)值沒有占位符 // 創(chuàng)建StaticTextSqlNode并添加到集合中 contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // 子節(jié)點(diǎn)為元素節(jié)點(diǎn) // CURD節(jié)點(diǎn)的子節(jié)點(diǎn)中的元素節(jié)點(diǎn)只可能為<if>,<foreach>等動(dòng)態(tài)Sql標(biāo)簽節(jié)點(diǎn) String nodeName = child.getNode().getNodeName(); // 根據(jù)動(dòng)態(tài)Sql標(biāo)簽節(jié)點(diǎn)的名稱獲取對(duì)應(yīng)的處理器 NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } // 處理動(dòng)態(tài)Sql標(biāo)簽節(jié)點(diǎn) handler.handleNode(child, contents); // 設(shè)置isDynamic為true isDynamic = true; } } // 創(chuàng)建MixedSqlNode return new MixedSqlNode(contents); }
按照正常執(zhí)行流程調(diào)用parseDynamicTags() 時(shí),入?yún)⑹?strong>CURD標(biāo)簽節(jié)點(diǎn),此時(shí)會(huì)遍歷CURD標(biāo)簽節(jié)點(diǎn)的所有子節(jié)點(diǎn),基于每個(gè)子節(jié)點(diǎn)都會(huì)創(chuàng)建一個(gè)SqlNode然后添加到SqlNode集合contents中,最后將contents作為入?yún)?chuàng)建MixedSqlNode并返回。
SqlNode是一個(gè)接口,在parseDynamicTags() 方法中,可以知道,TextSqlNode實(shí)現(xiàn)了SqlNode接口,StaticTextSqlNode實(shí)現(xiàn)了SqlNode接口,所以當(dāng)節(jié)點(diǎn)的子節(jié)點(diǎn)是文本節(jié)點(diǎn)時(shí),如果文本值包含有${}
占位符,則創(chuàng)建TextSqlNode添加到contents中并設(shè)置isDynamic為true,如果文本值不包含${}
占位符,則創(chuàng)建StaticTextSqlNode并添加到contents中。
如果CURD標(biāo)簽節(jié)點(diǎn)的子節(jié)點(diǎn)是元素節(jié)點(diǎn),由于CURD標(biāo)簽節(jié)點(diǎn)的元素節(jié)點(diǎn)只可能為<if>,<foreach>等動(dòng)態(tài)SQL標(biāo)簽節(jié)點(diǎn),所以直接會(huì)設(shè)置isDynamic為true,同時(shí)還會(huì)調(diào)用動(dòng)態(tài)SQL標(biāo)簽節(jié)點(diǎn)對(duì)應(yīng)的處理器來生成SqlNode并添加到contents中。這里以<if>標(biāo)簽節(jié)點(diǎn)對(duì)應(yīng)的處理器的handleNode() 方法為例進(jìn)行說明,如下所示。
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 遞歸調(diào)用parseDynamicTags()解析<if>標(biāo)簽節(jié)點(diǎn) MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String test = nodeToHandle.getStringAttribute("test"); // 創(chuàng)建IfSqlNode IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); // 將IfSqlNode添加到contents中 targetContents.add(ifSqlNode); }
在<if>標(biāo)簽節(jié)點(diǎn)對(duì)應(yīng)的處理器的handleNode() 方法中,遞歸的調(diào)用了parseDynamicTags() 方法來解析<if>標(biāo)簽節(jié)點(diǎn),例如<where>,<foreach>等標(biāo)簽節(jié)點(diǎn)對(duì)應(yīng)的處理器的handleNode() 方法中也會(huì)遞歸調(diào)用parseDynamicTags() 方法,這是因?yàn)檫@些動(dòng)態(tài)SQL標(biāo)簽是可以嵌套使用的,比如<where>標(biāo)簽節(jié)點(diǎn)的子節(jié)點(diǎn)可以為<if>標(biāo)簽節(jié)點(diǎn)。通過上面的handleNode() 方法,大致可以知道MixedSqlNode和IfSqlNode也實(shí)現(xiàn)了SqlNode接口,下面看一下MixedSqlNode和IfSqlNode的實(shí)現(xià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) { contents.forEach(node -> node.apply(context)); return true; } } 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) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } }
其實(shí)到這里已經(jīng)逐漸清晰明了了,按照正常執(zhí)行流程調(diào)用parseDynamicTags() 方法時(shí),是為了將CURD標(biāo)簽節(jié)點(diǎn)的所有子節(jié)點(diǎn)根據(jù)子節(jié)點(diǎn)類型生成不同的SqlNode并放在MixedSqlNode中,然后將MixedSqlNode返回,但是CURD標(biāo)簽節(jié)點(diǎn)的子節(jié)點(diǎn)中如果存在動(dòng)態(tài)SQL標(biāo)簽節(jié)點(diǎn),因?yàn)檫@些動(dòng)態(tài)SQL標(biāo)簽節(jié)點(diǎn)也會(huì)有子節(jié)點(diǎn),所以此時(shí)會(huì)遞歸的調(diào)用parseDynamicTags() 方法,以解析動(dòng)態(tài)SQL標(biāo)簽節(jié)點(diǎn)的子節(jié)點(diǎn),同樣會(huì)將這些子節(jié)點(diǎn)生成SqlNode并放在MixedSqlNode中然后將MixedSqlNode返回,遞歸調(diào)用parseDynamicTags() 方法時(shí)得到的MixedSqlNode會(huì)保存在動(dòng)態(tài)SQL標(biāo)簽節(jié)點(diǎn)對(duì)應(yīng)的SqlNode中,比如IfSqlNode中就會(huì)將遞歸調(diào)用parseDynamicTags() 生成的MixedSqlNode賦值給IfSqlNode的contents字段。
不同的SqlNode都是可以包含彼此的,這是組合設(shè)計(jì)模式的應(yīng)用,SqlNode之間的關(guān)系如下所示。
SqlNode接口定義了一個(gè)方法,如下所示。
public interface SqlNode { boolean apply(DynamicContext context); }
每個(gè)SqlNode的apply() 方法中,除了實(shí)現(xiàn)自己本身的邏輯外,還會(huì)調(diào)用自己所持有的所有SqlNode的apply() 方法,最終逐層調(diào)用下去,所有SqlNode的apply() 方法均會(huì)被執(zhí)行。
四. DynamicSqlSource和RawSqlSource源碼分析
回到XMLScriptBuilder的parseScriptNode() 方法,該方法中會(huì)調(diào)用parseDynamicTags() 方法以解析CURD標(biāo)簽節(jié)點(diǎn)并得到MixedSqlNode,MixedSqlNode中含有被解析的CURD標(biāo)簽節(jié)點(diǎn)的所有子節(jié)點(diǎn)對(duì)應(yīng)的SqlNode,最后會(huì)基于MixedSqlNode創(chuàng)建DynamicSqlSource或者RawSqlSource,如果CURD標(biāo)簽中含有動(dòng)態(tài)SQL標(biāo)簽或者SQL語(yǔ)句中含有${}
占位符,則創(chuàng)建DynamicSqlSource,否則創(chuàng)建RawSqlSource。下面分別對(duì)DynamicSqlSource和RawSqlSource的實(shí)現(xiàn)進(jìn)行分析。
1. DynamicSqlSource源碼分析
DynamicSqlSource的實(shí)現(xiàn)如下所示。
public class DynamicSqlSource implements SqlSource { private final Configuration configuration; private final SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { // 構(gòu)造函數(shù)只是進(jìn)行了簡(jiǎn)單的賦值操作 this.configuration = configuration; this.rootSqlNode = rootSqlNode; } @Override public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); // 調(diào)用SqlNode的apply()方法完成Sql語(yǔ)句的生成 rootSqlNode.apply(context); // SqlSourceBuilder可以將Sql語(yǔ)句中的#{}占位符替換為? SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); // 將Sql語(yǔ)句中的#{}占位符替換為?,并生成一個(gè)StaticSqlSource SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); // StaticSqlSource中保存有動(dòng)態(tài)生成好的Sql語(yǔ)句,并且#{}占位符全部替換成了? BoundSql boundSql = sqlSource.getBoundSql(parameterObject); // 生成有序參數(shù)映射列表 context.getBindings().forEach(boundSql::setAdditionalParameter); return boundSql; } }
DynamicSqlSource的構(gòu)造函數(shù)只是進(jìn)行了簡(jiǎn)單的賦值操作,重點(diǎn)在于其getBoundSql() 方法,在getBoundSql() 方法中,先是調(diào)用DynamicSqlSource中的SqlNode的apply() 方法以完成動(dòng)態(tài)SQL語(yǔ)句的生成,此時(shí)生成的SQL語(yǔ)句中的占位符(如果有的話)為#{}
,然后再調(diào)用SqlSourceBuilder的parse() 方法將SQL語(yǔ)句中的占位符從#{}
替換為?
并基于替換占位符后的SQL語(yǔ)句生成一個(gè)StaticSqlSource并返回,這里可以看一下StaticSqlSource的實(shí)現(xiàn),如下所示。
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) { // 構(gòu)造函數(shù)只是進(jìn)行簡(jiǎn)單的賦值操作 this.sql = sql; this.parameterMappings = parameterMappings; this.configuration = configuration; } @Override public BoundSql getBoundSql(Object parameterObject) { // 基于Sql語(yǔ)句創(chuàng)建一個(gè)BoundSql并返回 return new BoundSql(configuration, sql, parameterMappings, parameterObject); } }
所以分析到這里,可以知道DynamicSqlSource的getBoundSql() 方法實(shí)際上會(huì)完成動(dòng)態(tài)SQL語(yǔ)句的生成和#{}
占位符替換,然后基于生成好的SQL語(yǔ)句創(chuàng)建BoundSql并返回。BoundSql對(duì)象的類圖如下所示。
實(shí)際上,MyBatis中執(zhí)行SQL語(yǔ)句時(shí),如果映射文件中的SQL使用到了動(dòng)態(tài)SQL標(biāo)簽,那么MyBatis中的Executor(執(zhí)行器,后續(xù)文章中會(huì)進(jìn)行介紹)會(huì)調(diào)用MappedStatement的getBoundSql() 方法,然后在MappedStatement的getBoundSql() 方法中又會(huì)調(diào)用DynamicSqlSource的getBoundSql() 方法,所以MyBatis中的動(dòng)態(tài)SQL語(yǔ)句會(huì)在這條語(yǔ)句實(shí)際要執(zhí)行時(shí)才會(huì)生成。
2. RawSqlSource源碼分析
現(xiàn)在看一下RawSqlSource的實(shí)現(xiàn),如下所示。
public class RawSqlSource implements SqlSource { private final SqlSource sqlSource; public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) { // 先調(diào)用getSql()方法獲取Sql語(yǔ)句 // 然后再執(zhí)行構(gòu)造函數(shù) this(configuration, getSql(configuration, rootSqlNode), parameterType); } public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) { SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> clazz = parameterType == null ? Object.class : parameterType; // 將Sql語(yǔ)句中的#{}占位符替換為?,生成一個(gè)StaticSqlSource并賦值給sqlSource sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>()); } private static String getSql(Configuration configuration, SqlNode rootSqlNode) { DynamicContext context = new DynamicContext(configuration, null); rootSqlNode.apply(context); return context.getSql(); } @Override public BoundSql getBoundSql(Object parameterObject) { // 實(shí)際是調(diào)用StaticSqlSource的getBoundSql()方法 return sqlSource.getBoundSql(parameterObject); } }
RawSqlSource會(huì)在構(gòu)造函數(shù)中就將SQL語(yǔ)句生成好并替換#{}
占位符,在SQL語(yǔ)句實(shí)際要執(zhí)行時(shí),就直接將生成好的SQL語(yǔ)句返回。所以MyBatis中,靜態(tài)SQL語(yǔ)句的執(zhí)行通常要快于動(dòng)態(tài)SQL語(yǔ)句的執(zhí)行,這在RawSqlSource類的注釋中也有提及,如下所示。
Static SqlSource. It is faster than {@link DynamicSqlSource} because mappings are calculated during startup.
總結(jié)
MyBatis會(huì)為映射文件中的每個(gè)CURD標(biāo)簽節(jié)點(diǎn)里的SQL語(yǔ)句生成一個(gè)SqlSource:
- 如果是靜態(tài)SQL語(yǔ)句,那么會(huì)生成RawSqlSource;
- 如果是動(dòng)態(tài)SQL語(yǔ)句,則會(huì)生成DynamicSqlSource。
MyBatis在生成SqlSource時(shí),會(huì)為CURD標(biāo)簽節(jié)點(diǎn)的每個(gè)子節(jié)點(diǎn)都生成一個(gè)SqlNode,無論子節(jié)點(diǎn)是文本值節(jié)點(diǎn)還是動(dòng)態(tài)SQL元素節(jié)點(diǎn),最終所有子節(jié)點(diǎn)對(duì)應(yīng)的SqlNode都會(huì)放在SqlSource中以供生成SQL語(yǔ)句使用。
如果是靜態(tài)SQL語(yǔ)句,那么在創(chuàng)建RawSqlSource時(shí)就會(huì)使用SqlNode完成SQL語(yǔ)句的生成以及將SQL語(yǔ)句中的#{}
占位符替換為?
,然后保存在RawSqlSource中,等到這條靜態(tài)SQL語(yǔ)句要被執(zhí)行時(shí),就直接返回這條靜態(tài)SQL語(yǔ)句。
如果是動(dòng)態(tài)SQL語(yǔ)句,在創(chuàng)建DynamicSqlSource時(shí)只會(huì)簡(jiǎn)單的將SqlNode保存下來,等到這條動(dòng)態(tài)SQL語(yǔ)句要被執(zhí)行時(shí),才會(huì)使用SqlNode完成SQL語(yǔ)句的生成以及將SQL語(yǔ)句中的#{}
占位符替換為?
,最后返回SQL語(yǔ)句。
所以MyBatis
中,靜態(tài)SQL
語(yǔ)句的獲取要快于動(dòng)態(tài)SQL
語(yǔ)句。
以上就是詳解MyBatis的動(dòng)態(tài)SQL實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于MyBatis 動(dòng)態(tài)SQL實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- MyBatis之關(guān)于動(dòng)態(tài)SQL解讀
- MyBatis動(dòng)態(tài)SQL、模糊查詢與結(jié)果映射操作過程
- MybatisPlus使用Mybatis的XML的動(dòng)態(tài)SQL的功能實(shí)現(xiàn)多表查詢
- Mybatis使用注解實(shí)現(xiàn)復(fù)雜動(dòng)態(tài)SQL的方法詳解
- Mybatis使用XML實(shí)現(xiàn)動(dòng)態(tài)sql的示例代碼
- 詳解MyBatis特性之動(dòng)態(tài)SQL
- MyBatis映射文件中的動(dòng)態(tài)SQL實(shí)例詳解
- MyBatis中的XML實(shí)現(xiàn)和動(dòng)態(tài)SQL實(shí)現(xiàn)示例詳解
- Mybatis動(dòng)態(tài)Sql標(biāo)簽使用小結(jié)
- Mybatis之動(dòng)態(tài)SQL使用小結(jié)(全網(wǎng)最新)
- MyBatis實(shí)現(xiàn)動(dòng)態(tài)SQL的方法
相關(guān)文章
jmeter設(shè)置全局變量與正則表達(dá)式提取器過程圖解
這篇文章主要介紹了jmeter設(shè)置全局變量與正則表達(dá)式提取器過程圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10SpringBoot整合Hashids實(shí)現(xiàn)數(shù)據(jù)ID加密隱藏的全過程
這篇文章主要為大家詳細(xì)介紹了SpringBoot整合Hashids實(shí)現(xiàn)數(shù)據(jù)ID加密隱藏的全過程,文中的示例代碼講解詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01Java中public關(guān)鍵字用法詳細(xì)講解
這篇文章主要給大家介紹了關(guān)于Java中public關(guān)鍵字用法的相關(guān)資料,public關(guān)鍵字是和訪問權(quán)限相關(guān)的,它所修飾的方法對(duì)所有類都是可以訪問的,需要的朋友可以參考下2023-09-09基于spring如何實(shí)現(xiàn)事件驅(qū)動(dòng)實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于基于spring如何實(shí)現(xiàn)事件驅(qū)動(dòng)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04