Spring mybatis源碼篇章-動態SQL基礎語法以及原理


通過閱讀源碼對實現機制進行了解有利於陶冶情操,承接前文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,后文會分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM