最近有小伙伴在討論#{}與${}的區別時,有提到#{}是用字符串進行替換,就我個人的理解,它的主要作用是占位,最終替換的結果并不一定是字符串方式,比如我們傳參類型是整形時,最終拼接的sql,傳參講道理也應該是整形,而不是字符串的方式
接下來我們來看一下,mapper接口中不同的參數類型,最終拼接sql中是如何進行替換的
I. 環境配置
我們使用SpringBoot + Mybatis + MySql來搭建實例demo
springboot: 2.2.0.RELEASE
mysql: 5.7.22
1. 項目配置
1
2
3
4
5
6
7
8
9
10
11
|
< dependencies > < dependency > < groupId >org.mybatis.spring.boot</ groupId > < artifactId >mybatis-spring-boot-starter</ artifactId > < version >2.2.0</ version > </ dependency > < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > </ dependency > </ dependencies > |
核心的依賴mybatis-spring-boot-starter,至于版本選擇,到mvn倉庫中,找最新的
另外一個不可獲取的就是db配置信息,appliaction.yml
1
2
3
4
5
|
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: |
2. 數據庫表
用于測試的數據庫
1
2
3
4
5
6
7
8
9
10
|
CREATE TABLE `money` ( `id` int (11) unsigned NOT NULL AUTO_INCREMENT, ` name ` varchar (20) NOT NULL DEFAULT '' COMMENT '用戶名' , `money` int (26) NOT NULL DEFAULT '0' COMMENT '錢' , `is_deleted` tinyint(1) NOT NULL DEFAULT '0' , `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間' , `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間' , PRIMARY KEY (`id`), KEY ` name ` (` name `) ) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4; |
測試數據,主要是name字段,值為一個數字的字符串
1
2
3
|
INSERT INTO `money` (`id`, ` name `, `money`, `is_deleted`, `create_at`, `update_at`) VALUES (120, '120' , 200, 0, '2021-05-24 20:04:39' , '2021-09-27 19:21:40' ); |
II. 傳參類型確定
本文忽略掉mybatis中的po、mapper接口、xml文件的詳情,有興趣的小伙伴可以直接查看最下面的源碼(或者查看之前的博文也可以)
1. 參數類型為整形
針對上面的case,定義一個根據name查詢數據的接口,但是這個name參數類型為整數
mapper接口:
1
2
3
4
5
6
|
/** * int類型,最終的sql中參數替換的也是int * @param name * @return */ List<MoneyPo> queryByName( @Param ( "name" ) Integer name); |
對應的xml文件如下
1
2
3
|
< select id = "queryByName" resultMap = "BaseResultMap" > select * from money where `name` = #{name} </ select > |
上面這個寫法非常常見了,我們現在的問題就是,傳參為整數,那么最終的sql是 name = 120 還是 name = '120'呢?
那么怎么確定最終生成的sql是啥樣的呢?這里介紹一個直接輸出mysql執行sql日志的方式
在mysql服務器上執行下面兩個命令,開啟sql執行日志
1
2
|
set global general_log = "ON" ; show variables like 'general_log%' ; |
當我們訪問上面的接口之后,會發現最終發送給mysql的sql語句中,參數替換之后依然是整數
1
|
select * from money where ` name ` = 120 |
2. 指定jdbcType
在使用#{}, ${}時,有時也會看到除了參數之外,還會指定jdbcType,那么我們在xml中指定這個對最終的sql生成會有影響么?
1
2
3
|
< select id = "queryByNameV2" resultMap = "BaseResultMap" > select * from money where `name` = #{name, jdbcType=VARCHAR} and 0=0 </ select > |
生成的sql如下
1
|
select * from money where ` name ` = 120 and 0=0 |
從實際的sql來看,這個jdbcType并沒有影響最終的sql參數拼接,那它主要是干嘛用呢?(它主要適用于傳入null時,類型轉換可能出現的異常)
3. 傳參類型為String
當我們傳參類型為string時,最終的sql講道理應該會帶上引號
1
2
3
4
5
6
|
/** * 如果傳入的參數類型為string,會自動帶上'' * @param name * @return */ List<MoneyPo> queryByNameV3( @Param ( "name" ) String name); |
對應的xml
1
2
3
|
< select id = "queryByNameV3" resultMap = "BaseResultMap" > select * from money where `name` = #{name, jdbcType=VARCHAR} and 1=1 </ select > |
上面這個最終生成的sql如下
select * from money where `name` = '120' and 1=1
4. TypeHandler實現參數替換強制添加引號
看完上面幾節,基本上可以有一個得出一個簡單的推論(當然對不對則需要從源碼上分析了)
sql參數替換,最終并不是簡單使用字符串來替換,實際上是由參數java的參數類型決定,若java參數類型為字符串,拼接的sql為字符串格式;傳參為整型,拼接的sql也是整數
那么問題來了,為什么要了解這個?
關鍵點在于索引失效的問題
比如本文實例中的name上添加了索引,當我們的sql是 select * from money where name = 120 會走不了索引,如果想走索引,要求傳入的參數必須是字符串,不能出現隱式的類型轉換
基于此,我們就有一個應用場景了,為了避免由于傳參類型問題,導致走不了索引,我們希望name的傳參,不管實際傳入參數類型是什么,最終拼接的sql,都是字符串的格式;
我們借助自定義的TypeHandler來實現這個場景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
@MappedTypes (value = {Long. class , Integer. class }) @MappedJdbcTypes (value = {JdbcType.CHAR, JdbcType.VARCHAR, JdbcType.LONGVARCHAR}) public class StrTypeHandler extends BaseTypeHandler<Object> { /** * java 類型轉 jdbc類型 * * @param ps * @param i * @param parameter * @param jdbcType * @throws SQLException */ @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, String.valueOf(parameter)); } /** * jdbc類型轉java類型 * * @param rs * @param columnName * @return * @throws SQLException */ @Override public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } } |
然后在xml中,指定TypeHandler
1
2
3
4
5
6
|
/** * 通過自定義的 TypeHandler 來實現 java <-> jdbc 類型的互轉,從而實現即時傳入的是int/long,也會轉成String * @param name * @return */ List<MoneyPo> queryByNameV4( @Param ( "name" ) Integer name); |
1
2
3
|
< select id = "queryByNameV4" resultMap = "BaseResultMap" > select * from money where `name` = #{name, jdbcType=VARCHAR, typeHandler=com.git.hui.boot.mybatis.handler.StrTypeHandler} and 2=2 </ select > |
上面這種寫法輸出的sql就會攜帶上單引號,這樣就可以從源頭上解決傳參類型不對,導致最終走不了索引的問題
1
|
select * from money where ` name ` = '120' and 2=2 |
5. 小結
本文通過一個簡單的實例,來測試Mapper接口中,不同的參數類型,對最終的sql生成的影響
參數類型為整數時,最終的sql的參數替換也是整數(#{}并不是簡單的字符串替換哦)
參數類型為字符串時,最終的sql參數替換,會自動攜帶'' (${}注意它不會自動帶上單引號,需要自己手動添加)
當我們希望不管傳參什么類型,最終生成的sql,都是字符串替換時,可以借助自定義的TypeHandler來實現,這樣可以從源頭上避免因為隱式類型轉換導致走不了索引問題
最后疑問來了,上面的結論靠譜么?mybatis中最終的sql是在什么地方拼接的?這個sql拼接的流程是怎樣的呢?
關于sql的拼接全流程,后續博文即將上線,我是一灰灰,走過路過的各位大佬幫忙點個贊、價格收藏、給個評價唄
到此這篇關于Mybatis傳參類型如何確定的文章就介紹到這了,更多相關Mybatis傳參類型如何確定內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://juejin.cn/post/7023341996232310814