Mybatis中SQL的執(zhí)行過程詳解
Mybatis 框架
SQL執(zhí)行過程
數(shù)據(jù)庫(kù)操作映射方式
MyBatis支持兩種方式進(jìn)行數(shù)據(jù)庫(kù)操作映射:
- 映射文件:通過XML文件來定義SQL語(yǔ)句和映射關(guān)系
- 注解方式:通過在Java代碼中使用注解來定義SQL語(yǔ)句和映射關(guān)系
這兩種方式都可以實(shí)現(xiàn)數(shù)據(jù)庫(kù)操作的映射,具體使用哪種方式取決于個(gè)人的喜好和項(xiàng)目需求。本文都以映射文件的方式進(jìn)行分析。
SQL的執(zhí)行過程
SQL解析
MyBatis 會(huì)將SQL 語(yǔ)句解析為內(nèi)部的數(shù)據(jù)結(jié)構(gòu)。這一過程中,MyBatis 會(huì)根據(jù) SQL 中的參數(shù)類型、數(shù)量等信息,生成一個(gè) MappedStatement 對(duì)象,表示當(dāng)前的 SQL 語(yǔ)句及其相關(guān)的執(zhí)行元數(shù)據(jù)。
參數(shù)綁定
在 SQL 解析完成后,MyBatis 會(huì)在 SQL 參數(shù)映射階段,將 SQL 語(yǔ)句中的參數(shù)與 Java 對(duì)象進(jìn)行映射,并將參數(shù)值處理成符合數(shù)據(jù)庫(kù)預(yù)編譯要求的形式。這樣,在 SQL 預(yù)編譯和執(zhí)行階段,就可以正確地傳遞參數(shù)并執(zhí)行 SQL 語(yǔ)句。
SQL預(yù)編譯
MyBatis 利用 JDBC 的 PreparedStatement 接口進(jìn)行 SQL 語(yǔ)句的預(yù)編譯。預(yù)編譯過程會(huì)將 SQL 中的占位符(?)替換為實(shí)際參數(shù),并將其轉(zhuǎn)化為數(shù)據(jù)庫(kù)可以優(yōu)化執(zhí)行的形式,從而提高 SQL 執(zhí)行效率。
執(zhí)SQL行
完成預(yù)編譯后,MyBatis 會(huì)通過 JDBC API 將 SQL 語(yǔ)句提交給數(shù)據(jù)庫(kù)執(zhí)行,并獲取查詢結(jié)果或執(zhí)行結(jié)果。數(shù)據(jù)庫(kù)執(zhí)行完成后,結(jié)果會(huì)返回給 MyBatis 進(jìn)行進(jìn)一步處理。
結(jié)果映射
MyBatis 會(huì)將數(shù)據(jù)庫(kù)返回的結(jié)果集轉(zhuǎn)換為相應(yīng)的 Java 對(duì)象。這一過程包括類型轉(zhuǎn)換、字段匹配等,通常通過 TypeHandler 來完成。TypeHandler 負(fù)責(zé)將數(shù)據(jù)庫(kù)中的列值與 Java 對(duì)象的屬性進(jìn)行映射。
事務(wù)處理
如果啟用了手動(dòng)事務(wù)管理,MyBatis 會(huì)在 SQL 執(zhí)行完成后,根據(jù)配置的事務(wù)管理策略,提交或回滾事務(wù),確保數(shù)據(jù)一致性和事務(wù)的完整性。
緩存處理
若啟用了緩存,MyBatis 會(huì)將查詢結(jié)果存儲(chǔ)到緩存中,以便下次執(zhí)行相同的 SQL 語(yǔ)句時(shí)能夠直接從緩存中獲取結(jié)果,從而減少數(shù)據(jù)庫(kù)的訪問頻率,提高性能。MyBatis 支持一級(jí)緩存(SqlSession 范圍內(nèi))和二級(jí)緩存(跨 SqlSession 的共享緩存)。
日志記錄與監(jiān)控
MyBatis 會(huì)通過集成日志框架(如 Log4j、SLF4J)記錄 SQL 執(zhí)行的詳細(xì)日志。這些日志信息可以幫助開發(fā)人員分析 SQL 執(zhí)行的性能,發(fā)現(xiàn)潛在的問題,優(yōu)化查詢效率。
最后,將查詢結(jié)果返回給調(diào)用方,自此,完成整個(gè) SQL 執(zhí)行過程。
下面詳細(xì)介紹每一個(gè)過程:
SQL解析
當(dāng) MyBatis 執(zhí)行一個(gè)查詢時(shí),首先會(huì)對(duì)傳入的 SQL 語(yǔ)句進(jìn)行解析,解析 SQL 語(yǔ)句的結(jié)構(gòu)和參數(shù)信息,為后續(xù)的參數(shù)綁定和執(zhí)行做準(zhǔn)備。
- 解析過程會(huì)進(jìn)行語(yǔ)法分析和語(yǔ)義分析,以確保SQL語(yǔ)句的正確性
- 分析 SQL 語(yǔ)句中的參數(shù)占位符并提取出需要傳入的參數(shù)類型和數(shù)量。
- 將 SQL 語(yǔ)句轉(zhuǎn)化為 MyBatis 內(nèi)部可識(shí)別的數(shù)據(jù)結(jié)構(gòu)
MyBatis 使用 XML 或注解中的 SQL 語(yǔ)句,結(jié)合映射文件中的 MappedStatement 對(duì)象來表示 SQL 信息。
MappedStatement 是 MyBatis 核心的配置對(duì)象,包含了 SQL 的元數(shù)據(jù)、類型處理器、緩存策略等。
解析過程涉及到標(biāo)簽的解析、參數(shù)的解析和動(dòng)態(tài)SQL的處理
標(biāo)簽解析:
- 首先會(huì)解析映射文件的根節(jié)點(diǎn)(<select>、<insert>、<update>、<delete>等),然后逐個(gè)解析其中的標(biāo)簽。
- 解析標(biāo)簽時(shí),MyBatis會(huì)根據(jù)標(biāo)簽的不同類型(如查詢、插入、更新、刪除)執(zhí)行相應(yīng)的解析邏輯。
動(dòng)態(tài)SQL處理:
- MyBatis支持動(dòng)態(tài)SQL,即根據(jù)不同的條件在運(yùn)行時(shí)動(dòng)態(tài)生成SQL語(yǔ)句。\
- 動(dòng)態(tài)SQL使用一系列XML標(biāo)簽(如<if>、<choose>、<when>、<otherwise>等)進(jìn)行條件判斷和 SQL片段的組裝。
- 在解析映射文件時(shí),MyBatis會(huì)對(duì)動(dòng)態(tài)SQL片段進(jìn)行解析,并根據(jù)條件判斷生成最終的SQL語(yǔ)句。
- 動(dòng)態(tài)SQL的處理涉及到條件判斷、循環(huán)、字符串拼接等操作,以根據(jù)運(yùn)行時(shí)的條件生成最終的SQL語(yǔ)句。
參考:
<select id="findUserById" resultType="User"> SELECT * FROM users WHERE id = #{id} </select>
- 在上述例子中,findUserById 是一個(gè) MappedStatement 對(duì)象,#{id} 是一個(gè)占位符。
SQL參數(shù)映射
MyBatis 在 SQL 參數(shù)映射階段,會(huì)將用戶提供的參數(shù)綁定到 SQL 語(yǔ)句中的占位符。根據(jù)映射文件或注解中的 SQL 語(yǔ)句,MyBatis 會(huì)分析哪些占位符需要綁定哪些參數(shù)。這樣,在 SQL 預(yù)編譯和執(zhí)行階段,就可以正確地傳遞參數(shù)并執(zhí)行 SQL 語(yǔ)句。
在 MappedStatement 中,有一個(gè) BoundSql 對(duì)象,它包含了實(shí)際的 SQL 語(yǔ)句以及綁定的參數(shù)。BoundSql 會(huì)根據(jù) SQL 的占位符(如 ? 或 #{})將傳入的參數(shù)與 SQL 語(yǔ)句進(jìn)行綁定。
- MyBatis 首先會(huì)解析 SQL 語(yǔ)句,識(shí)別其中的占位符(#{})或者字符串替換(${})形式的參數(shù)。
- 對(duì)于 ${} 形式的參數(shù),MyBatis 會(huì)直接將參數(shù)值替換到 SQL 語(yǔ)句中(需要謹(jǐn)防SQL注入問題)。
參考:
Map<String, Object> params = new HashMap<>(); params.put("id", 1); userMapper.findUserById(params);
- params 中的 id 將會(huì)綁定到 SQL 語(yǔ)句中的 #{id} 占位符。
SQL預(yù)編譯
MyBatis使用JDBC的PreparedStatement接口創(chuàng)建預(yù)編譯的SQL語(yǔ)句,預(yù)編譯的SQL語(yǔ)句中使用占位符(如?)代替參數(shù)。通過 SQL 預(yù)編譯提升執(zhí)行效率,并確保參數(shù)的安全性。
- 預(yù)編譯有助于提高執(zhí)行效率,因?yàn)?SQL 語(yǔ)句只需要解析一次,數(shù)據(jù)庫(kù)會(huì)優(yōu)化執(zhí)行計(jì)劃,避免每次都解析 SQL。
- PreparedStatement 允許 MyBatis 在數(shù)據(jù)庫(kù)執(zhí)行時(shí)才將參數(shù)值填充到預(yù)編譯的 SQL 中,從而提高執(zhí)行效率并避免 SQL 注入攻擊。
參考:
PreparedStatement ps = connection.prepareStatement("SELECT * FROM users WHERE id = ?"); ps.setInt(1, 1);
- 將第一個(gè)參數(shù)(id)設(shè)置為 1
SQL執(zhí)行
MyBatis 會(huì)通過 JDBC API 將編譯后的 SQL 語(yǔ)句提交到數(shù)據(jù)庫(kù)執(zhí)行。執(zhí)行過程會(huì)獲取執(zhí)行結(jié)果,例如查詢結(jié)果或更新、刪除操作的影響行數(shù)。
- 執(zhí)行時(shí),MyBatis 會(huì)通過 Statement 或 PreparedStatement 接口執(zhí)行 SQL。查詢操作會(huì)返回一個(gè) ResultSet,更新、刪除等操作會(huì)返回受影響的行數(shù)。
- 如果執(zhí)行的是查詢操作,MyBatis 會(huì)進(jìn)一步處理返回的 ResultSet,并將其轉(zhuǎn)換為 Java 對(duì)象。
參考:
ResultSet rs = ps.executeQuery();
結(jié)果映射
執(zhí)行 SQL 后,MyBatis 會(huì)將數(shù)據(jù)庫(kù)返回的 ResultSet 映射為相應(yīng)的 Java 對(duì)象。這個(gè)映射過程會(huì)根據(jù) SQL 查詢的字段與 Java 對(duì)象的屬性進(jìn)行轉(zhuǎn)換。
- MyBatis 使用 ResultMap 來定義如何將查詢結(jié)果映射到 Java 對(duì)象。ResultMap 可以指定每個(gè)字段與對(duì)象屬性之間的映射關(guān)系。
- 例如,使用 TypeHandler 進(jìn)行類型轉(zhuǎn)換(比如將數(shù)據(jù)庫(kù)中的 VARCHAR 類型轉(zhuǎn)換為 String 類型)。
- 對(duì)于復(fù)雜的結(jié)果映射,MyBatis 允許嵌套的映射以及集合類型的處理。
參考:
<resultMap id="userResultMap" type="User"> <result property="id" column="id"/> <result property="name" column="name"/> </resultMap>
事務(wù)處理
MyBatis 支持手動(dòng)和自動(dòng)事務(wù)管理。默認(rèn)情況下,MyBatis 使用 JDBC 提供的事務(wù)控制。在執(zhí)行數(shù)據(jù)庫(kù)操作后,MyBatis 會(huì)根據(jù)配置決定是否提交或回滾事務(wù)。
- 如果使用自動(dòng)提交(autocommit=true),事務(wù)會(huì)在每個(gè)操作后立即提交。
- 如果使用手動(dòng)事務(wù)管理,開發(fā)者需要顯式地調(diào)用 commit() 或 rollback() 來提交或回滾事務(wù)。
- 事務(wù)處理保證了操作的一致性,即在一個(gè)事務(wù)中的多個(gè)操作要么全部成功,要么全部失敗。
參考:
sqlSession.commit(); // 提交事務(wù) sqlSession.rollback(); // 回滾事務(wù)
緩存處理
MyBatis 提供了一級(jí)緩存和二級(jí)緩存機(jī)制來提高查詢效率。一級(jí)緩存是 SqlSession 級(jí)別的緩存,而二級(jí)緩存是跨 SqlSession 的共享緩存。
- 一級(jí)緩存:在同一個(gè) SqlSession 中,查詢的結(jié)果會(huì)被緩存。只要沒有關(guān)閉 SqlSession,再次查詢相同的 SQL 會(huì)直接從緩存中獲取結(jié)果,而不是重新查詢數(shù)據(jù)庫(kù)。
- 二級(jí)緩存:不同 SqlSession 之間可以共享緩存。二級(jí)緩存的使用需要在 MyBatis 配置文件中顯式啟用,并且每個(gè)映射器可以配置自己的緩存策略。
- 緩存機(jī)制有助于提高性能,尤其是在查詢頻繁但數(shù)據(jù)變化較少的場(chǎng)景中。
日志記錄與監(jiān)控
MyBatis 可以集成各種日志框架(如 Log4j、SLF4J 等)來記錄 SQL 執(zhí)行過程中的信息。開發(fā)人員可以通過日志輸出 SQL 執(zhí)行的詳細(xì)信息,包括查詢語(yǔ)句、執(zhí)行時(shí)間等。
- MyBatis 的日志記錄對(duì)于開發(fā)人員調(diào)試 SQL 和優(yōu)化性能非常有幫助。
- 日志框架可以配置為不同的日志級(jí)別,例如 DEBUG、INFO、ERROR 等,方便查看不同詳細(xì)度的信息。
參考:
<settings> <setting name="logImpl" value="SLF4J"/> </settings>
擴(kuò)展
#與$的區(qū)別
$ 符號(hào)
- $符號(hào)占位符是簡(jiǎn)單的字符串替換,不進(jìn)行預(yù)編譯和參數(shù)類型處理,也不會(huì)進(jìn)行轉(zhuǎn)義。
- $符號(hào)占位符直接將參數(shù)的值替換到SQL語(yǔ)句中,可以用于動(dòng)態(tài)拼接SQL語(yǔ)句的部分內(nèi)容。
- $符號(hào)占位符存在SQL注入的風(fēng)險(xiǎn),因?yàn)閰?shù)值直接替換到SQL語(yǔ)句中,可能導(dǎo)致惡意注入攻擊。
- 最終SQL的動(dòng)態(tài)參數(shù)值一定不會(huì)有引號(hào)包裹
# 符號(hào)
- #符號(hào)占位符是預(yù)編譯的占位符,會(huì)對(duì)參數(shù)進(jìn)行類型處理和安全處理。
- #符號(hào)占位符將參數(shù)值作為預(yù)編譯參數(shù)傳遞給數(shù)據(jù)庫(kù),可以防止SQL注入攻擊。
- #符號(hào)占位符可以用于動(dòng)態(tài)生成SQL語(yǔ)句的條件部分,例如WHERE子句、ORDER BY子句等。
- 最終SQL的動(dòng)態(tài)參數(shù)值可能會(huì)有引號(hào)包裹(不是一定都會(huì)有引號(hào)包裹)
- 字符串類型:會(huì)自動(dòng)將其包裹在單引號(hào)中。
- 數(shù)字類型:不會(huì)自動(dòng)加單引號(hào)。
- 日期類型:會(huì)格式化日期并加上單引號(hào)。
總結(jié)示例
示例:
username的值為haha(String)
1. 使用$占位符
SELECT id, username, email FROM users WHERE username = ${username} # 最終實(shí)際執(zhí)行的sql SELECT id, username, email FROM users WHERE username = haha
2. 使用#占位符
SELECT id, username, email FROM users WHERE username = #{username} # 最終實(shí)際執(zhí)行的sql SELECT id, username, email FROM users WHERE username = 'haha'
Mybatis SQL分類
根據(jù) SQL 查詢的特性來區(qū)分的,可以將SQL分為動(dòng)態(tài) SQL 和靜態(tài) SQL。
動(dòng)態(tài) SQL
使用動(dòng)態(tài)SQL,可以根據(jù)不同的條件生成不同的SQL語(yǔ)句,從而實(shí)現(xiàn)靈活的查詢和更新操作。
動(dòng)態(tài)SQL可以使用if、choose、when、otherwise等標(biāo)簽來實(shí)現(xiàn)條件判斷和循環(huán)操作,同時(shí)還可以使用foreach標(biāo)簽來實(shí)現(xiàn)對(duì)集合類型參數(shù)的遍歷操作。
這樣可以避免在代碼中使用大量的字符串拼接,提高代碼的可讀性和維護(hù)性。
動(dòng)態(tài) SQL 的特點(diǎn):
- 可以根據(jù)不同的條件生成不同的 SQL 查詢。
- 可以在查詢語(yǔ)句中動(dòng)態(tài)地添加、刪除或修改部分 SQL 邏輯。
- 提供了更大的靈活性和動(dòng)態(tài)性,可以根據(jù)運(yùn)行時(shí)的條件來動(dòng)態(tài)生成查詢語(yǔ)句。
常見的動(dòng)態(tài) SQL 的示例:
- 使用條件語(yǔ)句(如 IF、CASE)來動(dòng)態(tài)選擇不同的查詢分支。
- 使用循環(huán)語(yǔ)句(如 FOR、WHILE)來生成重復(fù)或動(dòng)態(tài)數(shù)量的查詢條件。
- 使用動(dòng)態(tài)連接條件來構(gòu)建動(dòng)態(tài)查詢條件。
- 使用動(dòng)態(tài)排序來指定不同的排序方式。
靜態(tài) SQL
靜態(tài) SQL 的特點(diǎn):
- 查詢語(yǔ)句的結(jié)構(gòu)和邏輯是固定的,在執(zhí)行查詢時(shí)不會(huì)發(fā)生變化。
- SQL 查詢不受外部參數(shù)的影響,參數(shù)值在查詢中直接拼接。
常見的靜態(tài) SQL 的示例:
- 簡(jiǎn)單的選擇查詢語(yǔ)句,不需要根據(jù)條件改變查詢邏輯。
- 固定的更新語(yǔ)句,不受外部參數(shù)影響。
- 預(yù)定義的查詢模板,參數(shù)值直接拼接在查詢語(yǔ)句中。
- 創(chuàng)建表格、索引等靜態(tài)結(jié)構(gòu)定義。
靜態(tài)SQL和動(dòng)態(tài)SQL選擇
由于靜態(tài)sql是在應(yīng)用啟動(dòng)的時(shí)候就解析,而動(dòng)態(tài)sql是在執(zhí)行該sql相關(guān)操作的時(shí)候才根據(jù)傳入的參數(shù)進(jìn)行解析的,所以靜態(tài)sql效率會(huì)比動(dòng)態(tài)sql好。
${}、#{}與SQL是否靜態(tài)SQL、動(dòng)態(tài)SQL無直接關(guān)系
- ${} 和 #{} 只是占位符,用來插入?yún)?shù),它們本身并不決定 SQL 是靜態(tài)還是動(dòng)態(tài)。
- 動(dòng)態(tài) SQL 是通過 條件判斷(如 、)來決定 SQL 的結(jié)構(gòu)是否變化。
- 即使 SQL 中使用了 ${} 或 #{},如果沒有動(dòng)態(tài)條件,它仍然是靜態(tài) SQL。
示例1:
使用了${} 、#{},不是動(dòng)態(tài)SQL
<select id="getUserById" resultType="User"> SELECT id, username, email FROM users WHERE id = #{id} </select>
- 這里的 #{id} 用來傳遞參數(shù),但 SQL 本身 是固定的,沒有根據(jù)任何條件變化。
- 這個(gè)查詢并不是動(dòng)態(tài) SQL,因?yàn)樗鼪]有任何動(dòng)態(tài)部分(比如沒有使用 標(biāo)簽)。
示例2:
使用 ${} 來動(dòng)態(tài)拼接表名,不是動(dòng)態(tài)SQL
<select id="getUsersByTableName" resultType="User"> SELECT id, username, email FROM ${tableName} WHERE id = #{id} </select>
- 這里的 ${tableName} 是動(dòng)態(tài)的,會(huì)根據(jù)傳入的 tableName 參數(shù)拼接成不同的表名。
- 但 SQL 結(jié)構(gòu)本身依然是 固定的,只是表名不同。
示例3:
使用了${} 、#{},是動(dòng)態(tài)SQL
<select id="selectUsers" resultType="User"> SELECT id, username, email FROM users <where> <if test="username != null">AND username = #{username}</if> <if test="email != null">AND email = #{email}</if> </where> </select>
- 使用了 標(biāo)簽來動(dòng)態(tài)地添加 SQL 條件,因此整個(gè) SQL 是動(dòng)態(tài)的。
- 如果 username 和 email 參數(shù)存在,SQL 語(yǔ)句就會(huì)加入對(duì)應(yīng)的 AND 條件;如果不存在,這些條件會(huì)被忽略。
- 這個(gè)查詢才是真正的動(dòng)態(tài) SQL,因?yàn)樗慕Y(jié)構(gòu)根據(jù)輸入的參數(shù)變化。但 #{} 只是用來傳遞參數(shù),它和是否動(dòng)態(tài)沒有直接關(guān)系
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC+MyBatis 事務(wù)管理(實(shí)例)
本文先分析編程式注解事務(wù)和基于注解的聲明式事務(wù)。對(duì)SpringMVC+MyBatis 事務(wù)管理的相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2017-08-08Java打印出所有的水仙花數(shù)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java打印出所有的水仙花數(shù)的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-02-02JAVA中調(diào)用C語(yǔ)言函數(shù)的實(shí)現(xiàn)方式
這篇文章主要介紹了JAVA中調(diào)用C語(yǔ)言函數(shù)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08關(guān)于spring boot整合kafka+注解方式
這篇文章主要介紹了關(guān)于spring boot整合kafka+注解方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09spring-boot-maven-plugin報(bào)紅解決方案(親測(cè)有效)
本文主要介紹了spring-boot-maven-plugin報(bào)紅解決方案,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Java利用trueLicense實(shí)現(xiàn)項(xiàng)目離線證書授權(quán)操作步驟
文章介紹了如何使用trueLicense實(shí)現(xiàn)離線授權(quán)控制,包括生成公私鑰、創(chuàng)建證書校驗(yàn)?zāi)K、生成證書模塊和測(cè)試模塊,通過這種方式,可以控制用戶使用的項(xiàng)目模塊、授權(quán)周期、使用的設(shè)備和服務(wù)器,感興趣的朋友跟隨小編一起看看吧2024-11-11SpringBoot各種參數(shù)校驗(yàn)的實(shí)例教程
經(jīng)常需要提供接口與用戶交互(獲取數(shù)據(jù)、上傳數(shù)據(jù)等),由于這個(gè)過程需要用戶進(jìn)行相關(guān)的操作,為了避免出現(xiàn)一些錯(cuò)誤的數(shù)據(jù)等,一般需要對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),下面這篇文章主要給大家介紹了關(guān)于SpringBoot各種參數(shù)校驗(yàn)的相關(guān)資料,需要的朋友可以參考下2022-03-03