淺談Mybatis傳參類型如何確定
最近有小伙伴在討論#{}與${}的區(qū)別時(shí),有提到#{}是用字符串進(jìn)行替換,就我個(gè)人的理解,它的主要作用是占位,最終替換的結(jié)果并不一定是字符串方式,比如我們傳參類型是整形時(shí),最終拼接的sql,傳參講道理也應(yīng)該是整形,而不是字符串的方式
接下來(lái)我們來(lái)看一下,mapper接口中不同的參數(shù)類型,最終拼接sql中是如何進(jìn)行替換的
I. 環(huán)境配置
我們使用SpringBoot + Mybatis + MySql來(lái)搭建實(shí)例demo
springboot: 2.2.0.RELEASE
mysql: 5.7.22
1. 項(xiàng)目配置
<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倉(cāng)庫(kù)中,找最新的
另外一個(gè)不可獲取的就是db配置信息,appliaction.yml
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. 數(shù)據(jù)庫(kù)表
用于測(cè)試的數(shù)據(jù)庫(kù)
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 '錢(qián)', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4;
測(cè)試數(shù)據(jù),主要是name字段,值為一個(gè)數(shù)字的字符串
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. 參數(shù)類型為整形
針對(duì)上面的case,定義一個(gè)根據(jù)name查詢數(shù)據(jù)的接口,但是這個(gè)name參數(shù)類型為整數(shù)
mapper接口:
/** * int類型,最終的sql中參數(shù)替換的也是int * @param name * @return */ List<MoneyPo> queryByName(@Param("name") Integer name);
對(duì)應(yīng)的xml文件如下
<select id="queryByName" resultMap="BaseResultMap"> select * from money where `name` = #{name} </select>
上面這個(gè)寫(xiě)法非常常見(jiàn)了,我們現(xiàn)在的問(wèn)題就是,傳參為整數(shù),那么最終的sql是 name = 120 還是 name = '120'呢?
那么怎么確定最終生成的sql是啥樣的呢?這里介紹一個(gè)直接輸出mysql執(zhí)行sql日志的方式
在mysql服務(wù)器上執(zhí)行下面兩個(gè)命令,開(kāi)啟sql執(zhí)行日志
set global general_log = "ON"; show variables like 'general_log%';
當(dāng)我們?cè)L問(wèn)上面的接口之后,會(huì)發(fā)現(xiàn)最終發(fā)送給mysql的sql語(yǔ)句中,參數(shù)替換之后依然是整數(shù)
select * from money where `name` = 120
2. 指定jdbcType
在使用#{}, ${}時(shí),有時(shí)也會(huì)看到除了參數(shù)之外,還會(huì)指定jdbcType,那么我們?cè)趚ml中指定這個(gè)對(duì)最終的sql生成會(huì)有影響么?
<select id="queryByNameV2" resultMap="BaseResultMap"> select * from money where `name` = #{name, jdbcType=VARCHAR} and 0=0 </select>
生成的sql如下
select * from money where `name` = 120 and 0=0
從實(shí)際的sql來(lái)看,這個(gè)jdbcType并沒(méi)有影響最終的sql參數(shù)拼接,那它主要是干嘛用呢?(它主要適用于傳入null時(shí),類型轉(zhuǎn)換可能出現(xiàn)的異常)
3. 傳參類型為String
當(dāng)我們傳參類型為string時(shí),最終的sql講道理應(yīng)該會(huì)帶上引號(hào)
/** * 如果傳入的參數(shù)類型為string,會(huì)自動(dòng)帶上'' * @param name * @return */ List<MoneyPo> queryByNameV3(@Param("name") String name);
對(duì)應(yīng)的xml
<select id="queryByNameV3" resultMap="BaseResultMap"> select * from money where `name` = #{name, jdbcType=VARCHAR} and 1=1 </select>
上面這個(gè)最終生成的sql如下
select * from money where `name` = '120' and 1=1
4. TypeHandler實(shí)現(xiàn)參數(shù)替換強(qiáng)制添加引號(hào)
看完上面幾節(jié),基本上可以有一個(gè)得出一個(gè)簡(jiǎn)單的推論(當(dāng)然對(duì)不對(duì)則需要從源碼上分析了)
sql參數(shù)替換,最終并不是簡(jiǎn)單使用字符串來(lái)替換,實(shí)際上是由參數(shù)java的參數(shù)類型決定,若java參數(shù)類型為字符串,拼接的sql為字符串格式;傳參為整型,拼接的sql也是整數(shù)
那么問(wèn)題來(lái)了,為什么要了解這個(gè)?
關(guān)鍵點(diǎn)在于索引失效的問(wèn)題
比如本文實(shí)例中的name上添加了索引,當(dāng)我們的sql是 select * from money where name = 120 會(huì)走不了索引,如果想走索引,要求傳入的參數(shù)必須是字符串,不能出現(xiàn)隱式的類型轉(zhuǎn)換
基于此,我們就有一個(gè)應(yīng)用場(chǎng)景了,為了避免由于傳參類型問(wèn)題,導(dǎo)致走不了索引,我們希望name的傳參,不管實(shí)際傳入?yún)?shù)類型是什么,最終拼接的sql,都是字符串的格式;
我們借助自定義的TypeHandler來(lái)實(shí)現(xiàn)這個(gè)場(chǎng)景
@MappedTypes(value = {Long.class, Integer.class}) @MappedJdbcTypes(value = {JdbcType.CHAR, JdbcType.VARCHAR, JdbcType.LONGVARCHAR}) public class StrTypeHandler extends BaseTypeHandler<Object> { /** * java 類型轉(zhuǎn) 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類型轉(zhuǎn)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
/** * 通過(guò)自定義的 TypeHandler 來(lái)實(shí)現(xiàn) java <-> jdbc 類型的互轉(zhuǎn),從而實(shí)現(xiàn)即時(shí)傳入的是int/long,也會(huì)轉(zhuǎn)成String * @param name * @return */ List<MoneyPo> queryByNameV4(@Param("name") Integer name);
<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>
上面這種寫(xiě)法輸出的sql就會(huì)攜帶上單引號(hào),這樣就可以從源頭上解決傳參類型不對(duì),導(dǎo)致最終走不了索引的問(wèn)題
select * from money where `name` = '120' and 2=2
5. 小結(jié)
本文通過(guò)一個(gè)簡(jiǎn)單的實(shí)例,來(lái)測(cè)試Mapper接口中,不同的參數(shù)類型,對(duì)最終的sql生成的影響
參數(shù)類型為整數(shù)時(shí),最終的sql的參數(shù)替換也是整數(shù)(#{}并不是簡(jiǎn)單的字符串替換哦)
參數(shù)類型為字符串時(shí),最終的sql參數(shù)替換,會(huì)自動(dòng)攜帶'' (${}注意它不會(huì)自動(dòng)帶上單引號(hào),需要自己手動(dòng)添加)
當(dāng)我們希望不管傳參什么類型,最終生成的sql,都是字符串替換時(shí),可以借助自定義的TypeHandler來(lái)實(shí)現(xiàn),這樣可以從源頭上避免因?yàn)殡[式類型轉(zhuǎn)換導(dǎo)致走不了索引問(wèn)題
最后疑問(wèn)來(lái)了,上面的結(jié)論靠譜么?mybatis中最終的sql是在什么地方拼接的?這個(gè)sql拼接的流程是怎樣的呢?
關(guān)于sql的拼接全流程,后續(xù)博文即將上線,我是一灰灰,走過(guò)路過(guò)的各位大佬幫忙點(diǎn)個(gè)贊、價(jià)格收藏、給個(gè)評(píng)價(jià)唄
到此這篇關(guān)于Mybatis傳參類型如何確定的文章就介紹到這了,更多相關(guān)Mybatis傳參類型如何確定內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java為什么使用BlockingQueue解決競(jìng)態(tài)條件問(wèn)題面試精講
這篇文章主要為大家介紹了java為什么使用BlockingQueue解決競(jìng)態(tài)條件問(wèn)題面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Java使用lambda表達(dá)式簡(jiǎn)化代碼的示例詳解
這篇文章主要給大家介紹了Java如何使用lambda表達(dá)式簡(jiǎn)化代碼的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-11-11MyBatis創(chuàng)建存儲(chǔ)過(guò)程的實(shí)例代碼_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本節(jié)需要用到的有2部分,第一部分是如何在Derby中創(chuàng)建存儲(chǔ)過(guò)程,第二部分是如何在Mybatis中調(diào)用存儲(chǔ)過(guò)程,具體實(shí)例代碼大家參考下本文吧2017-09-09springboot整合rocketmq實(shí)現(xiàn)分布式事務(wù)
大多數(shù)情況下很多公司是使用消息隊(duì)列的方式實(shí)現(xiàn)分布式事務(wù)。 本篇文章重點(diǎn)講解springboot環(huán)境下整合rocketmq實(shí)現(xiàn)分布式事務(wù),感興趣的可以了解一下2021-05-05MyBatis的?級(jí)映射及延遲加載過(guò)程詳解
這篇文章主要介紹了MyBatis的?級(jí)映射及延遲加載,包括多對(duì)一延時(shí)加載方式及一對(duì)多,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02Spring Cloud Gateway自定義異常處理Exception Handler的方法小結(jié)
這篇文章主要介紹了Spring Cloud Gateway自定義異常處理Exception Handler的方法,本文通過(guò)兩種方法結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08Java NegativeArraySizeException異常解決方案
這篇文章主要介紹了Java NegativeArraySizeException異常解決方案,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08