通過閱讀源碼對實現機制進行了解有利於陶冶情操,承接前文Spring mybatis源碼篇章-Mybatis的XML文件加載
前話
前文通過Spring中配置mapperLocations屬性來進行對mybatis的XML文件的解析,本文將在前文的基礎上簡單的來看下Mybatis的使用的基礎語法以及解析原理
動態sql語法
具體的動態sql的使用可在官網查看Mybatis 3 | Dynamic SQL
1.if,條件判斷
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
與JAVA語意中的條件判斷是類似的
2.choose/when/otherwise,類似於if/else語句判斷
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
3.trim,多搭配where節點進行SQL語句拼裝
<!--prefix/suffix屬性均是由prefixOverrides/suffixOverrides的條件滿足后實行的覆蓋策略-->
<!--對開頭有AND或者OR的值直接替換為WHERE-->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
trim節點使用偏廣泛,可以與多個節點進行搭配使用
4.set,類比數據庫的set
關鍵字(其會消除多余的,分隔符)
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
5.foreach,集合迭代輸出
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
6.bind,創建額外屬性變量並應用
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
7._paramter和_databaseId是mybatis的內部屬性。
前者代表方法參數類,多與bind節點搭配使用;
后者則為mybatis主文件中databaseIdProvider
節點配置的
<databaseIdProvider type="VENDOR">
<property name="Oracle" value="oracle"></property>
<property name="Mysql" value="mysql"></property>
</databaseIdProvider>
抑或是可通過Spring來指定
<bean id="databaseIdProvider" class="org.apache.ibatis.mapping.VendorDatabaseIdProvider">
<property name="properties">
<props>
<prop key="Oracle">oracle</prop>
<prop key="Mysql">mysql</prop>
</props>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="databaseIdProvider" ref="databaseIdProvider" />
</bean>
值得注意的是,只有上述的樣例有配置過,方可使用_databaseId
屬性
XMLLanguageDriver
現在筆者將從源碼的角度來看下mybatis是如何去解析上述的節點
mybatis默認的XML驅動為XMLLanguageDriver。從這個類去深究一發
此處直接查看其主要方法createSqlSource()
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
引出XMLScriptBuilder#parseScriptNode()方法,我們再繼續觀察如何創建SqlSource對象(真實目的也是為了解析SQL節點)
public SqlSource parseScriptNode() {
//獲取select/update/insert/delete節點下的Sql語句節點包裝成相應的SqlNode對象
//每個CRUD語句可能都有多個SqlNode對象
List<SqlNode> contents = parseDynamicTags(context);
//包裝成混合型SqlNode對象,'Mixed'譯為混合,很貼切的名字
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
//是否為動態語句
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//否則生成普通版SqlSource
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
筆者此處簡單看下對各類節點的歸類處理,也就是去看下XMLScriptBuilder#parseDynamicTags()方法
private List<SqlNode> parseDynamicTags(XNode node) {
//獲取CRUD節點下所有子節點,包括文本內容<trim>等動態sql節點
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
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) {
//直接獲取數據內容,類似"select * from .."純文本語句
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
//TextSqlNode對象主要會去檢查純文本語句是否含有'${'和'}'字符串,有則為true
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
//返回最普通的含有data的StaticTextSqlNode對象
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
//獲取節點名對應的NodeHandler對象,無非就是TrimNodeHandler/WhereNodeHandler等等
NodeHandler handler = nodeHandlers.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//調用同一接口實現解析生成SqlNode對象並統一存入List<SqlNode>集合中
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
上述代碼通過parseDynamicTags()方法獲取的SqlNode集合是為了方便SqlSource類去真正的生成SQL語句
而org.apache.ibatis.scripting.xmltags.SqlNode是一個接口,內部只有一個apply()方法,其下有TrimSqlNode/IfSqlNode等實現類用於不同場景
小結
對上述的代碼以及講解作下簡單的小結
1.mybatis默認的mapper解析驅動為org.apache.ibatis.scripting.xmltags.XMLLanguageDriver類,基本上使用此類即可
2.用戶在使用
_databaseId
的時候,請確保相應的databaseIdProvider已配置3.mybatis動態SQL節點的解析最終都會被包裝成org.apache.ibatis.scripting.xmltags.MixedSqlSource類,里面包含了各種解析的SqlNode集合,其目的就是通過此接口來解析相應的動態SQL
4.最終保存在MappedStatement對象中的SqlSource類為org.apache.ibatis.scripting.xmltags.DynamicSqlSource,此類也比較關鍵,主要是用於返回String類型的SQL,后文會分析