MyBatis?動(dòng)態(tài)SQL使用及原理
引言
MyBatis 是一個(gè)優(yōu)秀的持久層框架,它提供了豐富的 SQL 映射功能,可以讓我們通過 XML 或注解方式來定義 SQL 語句。它很大程度上簡化了數(shù)據(jù)庫操作,提高了開發(fā)效率。動(dòng)態(tài) SQL 是其中一個(gè)非常重要的功能,可以讓我們根據(jù)不同的條件動(dòng)態(tài)生成 SQL 語句,提高了 SQL 的靈活性和可重用性。本文將詳細(xì)介紹 MyBatis 的動(dòng)態(tài) SQL 使用與原理。
1. 動(dòng)態(tài)SQL概述
動(dòng)態(tài)SQL是指根據(jù)條件拼接SQL語句的功能,可以在SQL語句中添加或者刪除某些條件和語句。在實(shí)際開發(fā)中,我們經(jīng)常需要根據(jù)不同的條件拼接不同的SQL語句。如果只使用靜態(tài)SQL,會(huì)使得代碼冗余度高、可讀性差、維護(hù)成本高等問題。而使用動(dòng)態(tài)SQL可以很好地解決這些問題。
MyBatis中提供了很多種方式來實(shí)現(xiàn)動(dòng)態(tài)SQL,包括if、choose、when、otherwise、trim、where、set等。
2. if標(biāo)簽
if標(biāo)簽是MyBatis中最常用的動(dòng)態(tài)SQL標(biāo)簽之一。它通常用來判斷條件是否成立,從而確定是否加入SQL語句中。下面是一段示例代碼:
<select id="selectUsers" resultMap="UserResultMap"> SELECT * FROM Users <where> <if test="name != null"> AND name = #{name} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>
上述代碼中,通過if標(biāo)簽的test屬性來判斷條件是否成立。只有當(dāng)"name"和"age"都不為空時(shí),才會(huì)將其加入到SQL語句中。這樣就可以在不同的情況下生成不同的SQL語句。
3. choose、when和otherwise標(biāo)簽
choose、when和otherwise標(biāo)簽通常一起使用,它類似于Java中的switch語句。下面是一段示例代碼:
<select id="selectUsers" resultMap="UserResultMap"> SELECT * FROM Users <where> <choose> <when test="name != null"> AND name = #{name} </when> <when test="age != null"> AND age = #{age} </when> <otherwise> AND id > 0 </otherwise> </choose> </where> </select>
choose、when和otherwise標(biāo)簽中,如果test條件成立,就會(huì)將當(dāng)前標(biāo)簽中的SQL語句加入到最終的SQL語句中。只有一個(gè)可以成立,多個(gè)成立時(shí)按順序第一個(gè)生效。
4. trim標(biāo)簽
trim標(biāo)簽通常用來去掉特定字符或者關(guān)鍵字。下面是一段示例代碼:
<select id="selectUsers" resultMap="UserResultMap"> SELECT * FROM Users <where> <trim prefix="AND" prefixOverrides="OR"> <if test="name != null"> OR name = #{name} </if> <if test="age != null"> OR age = #{age} </if> </trim> </where> </select>
上述代碼中,prefix屬性表示在標(biāo)簽內(nèi)部SQL語句前添加的字符;prefixOverrides屬性表示從標(biāo)簽內(nèi)部SQL語句開頭去除的字符串。
5. set標(biāo)簽和where標(biāo)簽
set標(biāo)簽通常用來更新參數(shù)對象中的非空屬性。where標(biāo)簽通常用來拼接SQL語句中的where條件。下面是一段示例代碼:
<update id="updateUser" parameterType="User"> UPDATE Users <set> <if test="name != null"> name = #{name}, </if> <if test="age != null"> age = #{age}, </if> </set> <where> id = #{id} </where> </update>
上述代碼中,set標(biāo)簽用來設(shè)置要更新的字段,通過if標(biāo)簽判斷哪些字段需要更新。where標(biāo)簽用來拼接SQL語句中的where條件,具體的條件可以根據(jù)實(shí)際情況進(jìn)行調(diào)整。
6. foreach
foreach 標(biāo)簽用于處理集合類型的參數(shù),比如 List、Array 等,可以遍歷集合中的元素,將每個(gè)元素都轉(zhuǎn)化為 SQL 語句的一部分,用于生成動(dòng)態(tài) SQL 語句。下面是一個(gè)示例:
<select id="getUserByIdList" resultType="User"> SELECT * FROM user WHERE id IN <foreach collection="idList" item="id" open="(" separator="," close=")"> #{id} </foreach> </select>
在上述 SQL 語句中,我們通過 foreach 標(biāo)簽遍歷傳入的參數(shù) idList,將其中的每個(gè)元素轉(zhuǎn)化為一個(gè) id,然后根據(jù)這些 id 拼接成一個(gè) IN 子句。
7. bind
bind 標(biāo)簽用于定義一個(gè)變量,該變量可以被后續(xù)的 SQL 片段引用,方便了 SQL 的編寫。下面是一個(gè)示例:
<select id="getUserByName" resultType="User"> <bind name="queryName" value="'%' + name + '%'"/> SELECT * FROM user WHERE name like #{queryName} </select>
在上述 SQL 語句中,我們使用 bind 標(biāo)簽定義了一個(gè)變量 queryName,它的值為 name 模糊查詢的條件。然后使用該變量來拼接 SQL 語句,使得 SQL 語句更加簡潔。
8. 動(dòng)態(tài)SQL解析原理
MyBatis的動(dòng)態(tài)SQL是通過OGNL表達(dá)式來實(shí)現(xiàn)的。OGNL(Object-Graph Navigation Language)是一種基于Java對象圖遍歷的表達(dá)式語言,它可以方便地訪問Java對象的屬性和方法。
在MyBatis中,通過OGNL表達(dá)式可以動(dòng)態(tài)地計(jì)算條件是否成立,從而確定是否將SQL片段添加到最終的SQL語句中。OGNL表達(dá)式通常嵌入在MyBatis中的動(dòng)態(tài)SQL標(biāo)簽中,例如if、choose、when、otherwise等。
MyBatis使用了兩個(gè)重要的類來實(shí)現(xiàn)OGNL表達(dá)式的解析和計(jì)算:OgnlExpressionEvaluator和OgnlCache。OgnlExpressionEvaluator類負(fù)責(zé)將MyBatis傳入的參數(shù)對象轉(zhuǎn)換為OGNL表達(dá)式需要的上下文對象,然后將OGNL表達(dá)式計(jì)算結(jié)果返回;OgnlCache類負(fù)責(zé)緩存已經(jīng)解析好的OGNL表達(dá)式,避免重復(fù)解析和計(jì)算。
具體的解析過程如下:
根據(jù)MyBatis的配置將Mapper.xml文件中的SQL語句解析為一個(gè)MappedStatement對象,并將其中的OGNL表達(dá)式解析成一個(gè)一個(gè)可執(zhí)行的SQL片段。
對于每一個(gè)OGNL表達(dá)式,MyBatis使用${}來表示一個(gè)簡單的OGNL表達(dá)式,使用#{}來表示一個(gè)OGNL表達(dá)式中包含復(fù)雜邏輯的情況。在解析過程中,MyBatis會(huì)將OGNL表達(dá)式中的參數(shù)進(jìn)行解析和預(yù)處理,然后使用OgnlCache類將其緩存起來。
當(dāng)Mapper接口方法被調(diào)用時(shí),MyBatis會(huì)將方法中傳入的參數(shù)對象轉(zhuǎn)換為一個(gè)BoundSql對象,并將該BoundSql對象與MappedStatement對象一起傳遞給OgnlExpressionEvaluator類。
OgnlExpressionEvaluator類中再次解析OGNL表達(dá)式,并將BoundSql對象作為上下文傳入OGNL表達(dá)式中執(zhí)行。OGNL表達(dá)式執(zhí)行的結(jié)果將被轉(zhuǎn)化為String類型,并返回給BoundSql對象。
最后,MyBatis將所有BoundSql對象中的SQL片段拼接成最終的SQL語句并執(zhí)行。
MyBatis的動(dòng)態(tài)SQL解析原理是將OGNL表達(dá)式解析為可執(zhí)行的SQL片段,然后根據(jù)條件判斷是否將該SQL片段加入到最終的SQL語句中。MyBatis使用OgnlExpressionEvaluator和OgnlCache類來實(shí)現(xiàn)OGNL表達(dá)式的解析和計(jì)算,從而實(shí)現(xiàn)動(dòng)態(tài)SQL的功能。
在 MyBatis 的源碼中,動(dòng)態(tài) SQL 還涉及到以下接口和類來實(shí)現(xiàn):
SqlNode
接口:表示一個(gè) SQL 節(jié)點(diǎn),也就是一個(gè) SQL 片段。它包含一個(gè)apply
方法,在執(zhí)行 SQL 語句時(shí)會(huì)將 SQL 片段應(yīng)用到相應(yīng)的位置。MixedSqlNode
類:實(shí)現(xiàn)了SqlNode
接口,可以包含多個(gè)子節(jié)點(diǎn)。該類的apply
方法會(huì)依次遍歷所有子節(jié)點(diǎn),并將每個(gè)節(jié)點(diǎn)應(yīng)用到 SQL 語句中。TextSqlNode
類:表示一個(gè)純文本節(jié)點(diǎn)。該類包含一個(gè)文本字符串,可以將其直接應(yīng)用到 SQL 語句中。IfSqlNode
類:表示一個(gè)條件節(jié)點(diǎn)??梢愿鶕?jù)指定的條件判斷是否需要應(yīng)用該節(jié)點(diǎn)內(nèi)部的 SQL 片段。如果條件成立,則會(huì)將 SQL 片段應(yīng)用到 SQL 語句中。TrimSqlNode
類:表示一個(gè)修剪節(jié)點(diǎn),可以根據(jù)配置對 SQL 片段進(jìn)行修剪操作。常用于處理 UPDATE 和 INSERT 語句中 SET 子句的逗號問題。WhereSqlNode
類:表示一個(gè) WHERE 條件節(jié)點(diǎn)??梢詫?WHERE 子句的參數(shù)拼接到 SQL 語句中。
以上是 MyBatis 中實(shí)現(xiàn)動(dòng)態(tài) SQL 的核心接口和類。MyBatis 內(nèi)部通過組合這些接口和類來構(gòu)建復(fù)雜的 SQL 語句。通過定義這些接口和類,可以讓開發(fā)者更加方便地書寫動(dòng)態(tài) SQL 語句,并且遵循了設(shè)計(jì)模式中的單一職責(zé)原則。
還有一些Builder
接口及其實(shí)現(xiàn)類的作用都是用于構(gòu)造 SQL 語句。下面簡單介紹一下一些常用的 Builder
類型:
BaseBuilder
接口:所有Builder
的基礎(chǔ)接口,定義了一些共同的方法,例如獲取Configuration
對象、創(chuàng)建ParameterMapping
對象等。XMLMapperBuilder
類:從 XML 文件中解析出各種 SQL 節(jié)點(diǎn),然后通過其他Builder
對象將其轉(zhuǎn)換成 SQL 語句。MapperBuilderAssistant
類:輔助XMLMapperBuilder
類創(chuàng)建各種類型的 SQL 節(jié)點(diǎn),例如創(chuàng)建<select>
、<update>
、<insert>
等標(biāo)簽節(jié)點(diǎn)。SqlSourceBuilder
類:根據(jù) XML 中的 SQL 片段創(chuàng)建SqlSource
對象,SqlSource
對象中包含了解析后的 SQL 語句和參數(shù)信息。DynamicSqlSource
類:用于處理動(dòng)態(tài) SQL,也就是包含各種條件判斷和循環(huán)語句的 SQL 片段。它是SqlSource
接口的一種實(shí)現(xiàn)。StaticSqlSource
類:用于處理靜態(tài) SQL,即不包含任何條件語句和循環(huán)語句的 SQL 片段。它同樣是SqlSource
接口的一種實(shí)現(xiàn)。SqlSessionFactoryBuilder
類:用于創(chuàng)建SqlSessionFactory
對象,它會(huì)將所有的Builder
對象組合在一起,完成 SQL 語句的解析和構(gòu)造。
通過上述不同類型的 Builder
對象,我們可以將 XML 中的 SQL 片段轉(zhuǎn)換成 Java 對象,并且根據(jù)各種條件生成相應(yīng)的 SQL 語句。這個(gè)過程中涉及到的類和方法非常多,需要我們深入地了解 MyBatis 的內(nèi)部實(shí)現(xiàn)才能靈活運(yùn)用。
9. 總結(jié)
本文通過介紹MyBatis動(dòng)態(tài)SQL的基本概念和常用標(biāo)簽(if、choose、when、otherwise、trim、where、set、foreach),希望讀者能夠更加深入地了解MyBatis的使用和原理。在實(shí)際開發(fā)過程中,要根據(jù)具體場景和需求選擇合適的動(dòng)態(tài)SQL標(biāo)簽,從而實(shí)現(xiàn)靈活拼接SQL語句的功能,提高開發(fā)效率。
在 MyBatis 中,動(dòng)態(tài) SQL 主要包括以下幾種類型:
<if>
標(biāo)簽:表示一個(gè)條件語句,可以根據(jù)條件判斷是否包含相應(yīng)的 SQL 片段。<where>
標(biāo)簽:表示一個(gè) WHERE 條件語句,可以根據(jù)配置自動(dòng)添加 WHERE 關(guān)鍵字。<choose>
標(biāo)簽:表示一個(gè)選擇語句,可以根據(jù)多個(gè)條件選擇符合條件的 SQL 片段。<foreach>
標(biāo)簽:表示一個(gè)循環(huán)語句,在循環(huán)中動(dòng)態(tài)生成 SQL 語句。<set>
標(biāo)簽:表示一個(gè) SET 子句,可以根據(jù)指定的屬性值動(dòng)態(tài)生成 SET 語句。
以上標(biāo)簽都屬于動(dòng)態(tài) SQL,在解析時(shí)需要通過特殊的方式進(jìn)行處理。下面以 <if>
標(biāo)簽為例介紹解析原理:
XMLScriptBuilder
類會(huì)根據(jù)標(biāo)簽類型創(chuàng)建相應(yīng)的 SQL 節(jié)點(diǎn),例如<if>
標(biāo)簽對應(yīng)的節(jié)點(diǎn)是IfSqlNode
對象。XMLScriptBuilder
類會(huì)遞歸解析節(jié)點(diǎn)內(nèi)部的子節(jié)點(diǎn),并將其組合成一個(gè) SQL 片段。- 當(dāng)解析到
IfSqlNode
節(jié)點(diǎn)時(shí),XMLScriptBuilder
類會(huì)獲取標(biāo)簽中的test
屬性,并根據(jù)該屬性值創(chuàng)建一個(gè)OgnlExpression
對象(OGNL 表達(dá)式對象),用于判斷條件是否滿足。 - 如果條件滿足,則將子節(jié)點(diǎn)生成的 SQL 片段添加到當(dāng)前 SQL 上下文中;否則忽略該節(jié)點(diǎn)。
- 最終生成的 SQL 語句就是將所有滿足條件的 SQL 片段組合起來得到的。
以上就是 MyBatis 實(shí)現(xiàn)動(dòng)態(tài) SQL 解析的大體流程。通過 XMLScriptBuilder
類的遞歸解析,可以將各種類型的動(dòng)態(tài) SQL 節(jié)點(diǎn)轉(zhuǎn)換成 SqlNode
接口的實(shí)現(xiàn),然后通過 MixedSqlNode
類將它們組合成一個(gè)完整的 SQL 片段。
以上就是MyBatis 動(dòng)態(tài)SQL使用及原理的詳細(xì)內(nèi)容,更多關(guān)于MyBatis 動(dòng)態(tài)SQL使用及原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mybatis-plus動(dòng)態(tài)表名的實(shí)現(xiàn)示例
這篇文章主要介紹了mybatis-plus動(dòng)態(tài)表名的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Java編程實(shí)現(xiàn)判斷網(wǎng)上鄰居文件是否存在的方法
這篇文章主要介紹了Java編程實(shí)現(xiàn)判斷網(wǎng)上鄰居文件是否存在的方法,涉及Java針對路徑轉(zhuǎn)換及字符串操作的相關(guān)技巧,需要的朋友可以參考下2015-10-10java模擬實(shí)現(xiàn)斗地主發(fā)牌小程序
這篇文章主要為大家詳細(xì)介紹了java模擬實(shí)現(xiàn)斗地主發(fā)牌小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04Java中的RASP機(jī)制實(shí)現(xiàn)詳解
這篇文章主要介紹了Java中的RASP實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08eclipse創(chuàng)建springboot項(xiàng)目的三種方式總結(jié)
這篇文章主要介紹了eclipse創(chuàng)建springboot項(xiàng)目的三種方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07mybatis 解決從列名到屬性名的自動(dòng)映射失敗問題
這篇文章主要介紹了mybatis 解決從列名到屬性名的自動(dòng)映射失敗問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java高效提取PDF文件指定坐標(biāo)的文本內(nèi)容實(shí)戰(zhàn)代碼
在日常工作中,有時(shí)可能會(huì)需要從龐大的PDF文檔中提取其中所包含的文本內(nèi)容,下面這篇文章主要給大家介紹了關(guān)于如何利用Java高效提取PDF文件指定坐標(biāo)的文本內(nèi)容,需要的朋友可以參考下2024-01-01