在基于Java的持久層框架MyBatis中,#{}與${}是處理SQL語句時兩種最為核心的參數占位符,它們雖然外觀相似,但在底層處理機制、安全性和應用場景上卻存在著本質的區別。正確理解并運用這兩者,是構建高效、安全數據訪問層的關鍵,也是數據庫連接池等技術能夠穩定、高效運行的重要前提。
一、核心機制差異:預編譯與字符串拼接
#{}(參數占位符)
其工作原理是預編譯(PreparedStatement)。MyBatis在解析SQL時,會將#{}替換為?,然后將SQL語句的編譯和參數的傳遞分離。數據庫驅動會先對SQL語句的結構進行編譯和優化,再將具體的參數值安全地設置進去。這個過程能有效防止SQL注入,因為參數值會被視為數據而非SQL指令的一部分。例如:`sql
SELECT FROM user WHERE id = #{userId}`
會被處理為類似于SELECT </em> FROM user WHERE id = ?的預編譯語句,然后將userId的實際值安全地傳遞。
${}(字符串替換符)
其工作原理是直接的字符串拼接。MyBatis在解析階段,會直接將${}內的內容(通常是一個OGNL表達式求值結果)以字符串的形式替換到SQL語句的對應位置,生成一條完整的SQL語句,然后提交給數據庫執行。例如:`sql
SELECT FROM ${tableName} ORDER BY ${orderColumn}`
如果tableName的值為"user",orderColumn的值為"create_time",則最終執行的SQL為:SELECT </em> FROM user ORDER BY create_time。
二、安全性對比:SQL注入風險
這是兩者最顯著也最關鍵的差異。
- #{} 高度安全:由于其預編譯特性,用戶輸入的內容無論如何變化,都只會被當作參數值處理,無法改變原SQL語句的結構,因此從根本上杜絕了SQL注入攻擊。
- ${} 存在風險:由于是直接拼接,如果
${}內的參數值來自不可信的用戶輸入(如前端表單),攻擊者可以精心構造輸入來改變SQL語義,導致數據泄露、篡改甚至刪除。例如,對于SELECT <em> FROM user WHERE id = ${inputId},如果inputId被傳入"1 OR 1=1",最終執行的SQL將變成SELECT </em> FROM user WHERE id = 1 OR 1=1,導致查詢出所有用戶數據。
安全準則:絕大多數情況下,應優先使用#{}。只有在參數值明確安全、且需要動態改變SQL結構(非數據值)的場景下,才謹慎考慮使用${}。
三、適用場景剖析
#{} 的典型場景:
1. WHERE條件中的值:這是最主要的使用場景,用于傳遞查詢、更新、刪除的條件值。
2. INSERT語句的VALUES值。
3. UPDATE語句的SET值。
簡單來說,幾乎所有傳遞具體數據值的地方都應使用#{}。
${} 的適用場景(需謹慎):
1. 動態表名或列名:當SQL語句需要根據參數動態指定數據庫表名或字段名時,因為這些內容是SQL結構的一部分,無法使用預編譯的?占位。如上文的${tableName}例子。
2. ORDER BY 排序字段:需要根據前端選擇動態排序時,如ORDER BY ${sortField} ${sortOrder}。
3. SQL函數或特殊關鍵字:如使用數據庫原生函數,如SELECT ${aggregateFunction}(column) FROM table。
關鍵提醒:在使用${}時,必須確保參數值不是來自不可信的直接用戶輸入。最佳實踐是,在服務端代碼中通過白名單機制進行校驗和映射,例如將前端傳入的"sortByTime"映射為數據庫列名"create_time",再傳遞給${}。
四、與數據庫連接池的協同
理解#{}與${}的差異,對于充分發揮數據庫連接池(如HikariCP, Druid等)的效能至關重要。
- 提升連接復用效率:預編譯語句(
#{}生成)是數據庫連接池優化的重點。數據庫驅動和服務器會對預編譯語句進行緩存(Statement Caching)。當相同的SQL結構(僅參數值不同)反復執行時,數據庫可以跳過語法解析和優化階段,直接使用緩存的執行計劃,極大提升了執行速度。連接池可以更好地管理和復用這些“已準備好”的語句句柄,減少數據庫端的資源開銷。 - 保障連接健康與安全:使用
#{}避免了SQL注入,從而防止了因惡意SQL導致的數據庫連接異常占用、資源耗竭或數據損壞,確保了連接池中每個連接的健康和穩定,維護了整個應用數據交互基石的牢固。 - ${}的潛在影響:濫用
${}會導致每次SQL語句都可能不同(因為字符串內容變了),數據庫無法有效緩存執行計劃,降低了執行效率。注入風險可能導致數據庫異常,間接影響連接池的穩定性和可用性。
###
#{}與${}的選擇,本質上是安全、性能與靈活性的權衡。
#{}是默認且首選的方案,它安全、高效,能與數據庫及連接池深度優化配合,是處理用戶輸入和業務數據的“安全衛士”。${}是特定場景下的工具,它提供了動態SQL的靈活性,但必須由開發者在確保參數值絕對安全的前提下審慎使用,是處理SQL結構動態性的“手術刀”。
筑牢數據交互的根基,始于對每一個SQL語句細節的精準把控。正確使用#{}與${},不僅是編寫MyBatis代碼的基本功,更是構建穩健、高性能數據持久層,讓數據庫連接池等基礎設施發揮最大效能的基石。