Mybatis系列第5篇:Mapper介面多種方式傳參詳解、原理、源碼解析
- 2019 年 12 月 10 日
- 筆記
Mybatis系列目標:從入門開始開始掌握一個高級開發所需要的Mybatis技能。
這是mybatis系列第5篇。
主要內容
本篇詳解mapper介面傳參的各種方式。
- 傳遞一個參數
- 傳遞一個Map參數
- 傳遞一個javabean參數
- 多參數中用@param指定參數名稱
- java編譯中參數名稱的處理
- mapper介面傳參源碼分析
- 傳遞1個Collection參數
- 傳遞1個List參數
- 傳遞1個數組參數
- mybatis對於集合處理源碼分析
- ResultHandler作為參數的用法
本篇文章的案例在上一篇chat03模組
上進行開發,大家可以到文章的尾部獲取整個mybatis系列的案例源碼。
mybatis系列的文章前後都是有依賴的,請大家按順序去看,盡量不要跳著去看,這樣不會出現看不懂的情況,建議大家系統化的學習知識,基礎打牢,慢慢才能成為高手。
使用mybatis開發項目的中,基本上都是使用mapper介面的方式來執行db操作,下面我們來看一下mapper介面傳遞參數的幾種方式及需要注意的地方。
傳遞一個參數
用法
Mapper介面方法中只有一個參數,如:
UserModel getByName(String name);
Mapper xml引用這個name參數:
#{任意合法名稱}
如:#{name}、#{val}、${x}等等寫法都可以引用上面name參數的值
。
案例
創建UserModel
類,如下:
package com.javacode2018.chat03.demo4.model; import lombok.*; /** * 公眾號:路人甲Java,工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活! */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @ToString public class UserModel { private Long id; private String name; private Integer age; private Double salary; private Integer sex; }
創建Mapper介面UserMapper
,如下:
package com.javacode2018.chat03.demo4.mapper; import com.javacode2018.chat03.demo4.model.UserModel; import java.util.List; import java.util.Map; /** * 公眾號:路人甲Java,工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活! */ public interface UserMapper { /** * 通過name查詢 * * @param name * @return */ UserModel getByName(String name); }
注意上面有個getByName方法,這個方法傳遞一個參數。
創建Mapper xml文件UserMapper.xml
,mybatis-serieschat03srcmainresourcescomjavacode2018chat03demo4mapper
目錄創建UserMapper.xml
,如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.javacode2018.chat03.demo4.mapper.UserMapper"> <!-- 通過name查詢 --> <select id="getByName" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user WHERE name = #{value} LIMIT 1 ]]> </select> </mapper>
上面有個getByName通過用戶名查詢,通過#{value}引用傳遞進來的name參數,當一個參數的時候
#{變數名稱}
中變數名稱可以隨意寫,都可以取到傳入的參數。
創建屬性配置文件,mybatis-serieschat03srcmainresources
目錄創建jdbc.properties
,如下:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8 jdbc.username=root jdbc.password=root123
上面是我本地db配置,大家可以根據自己db資訊做對應修改。
創建mybatis全局配置文件,mybatis-serieschat03srcmainresourcesdemo4
目錄創建mybatis-config.xml
,如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 引入外部jdbc配置 --> <properties resource="jdbc.properties"/> <!-- 環境配置,可以配置多個環境 --> <environments default="demo4"> <environment id="demo4"> <!-- 事務管理器工廠配置 --> <transactionManager type="JDBC"/> <!-- 數據源工廠配置,使用工廠來創建數據源 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <package name="com.javacode2018.chat03.demo4.mapper"/> </mappers> </configuration>
上面通過properties的resource屬性引入了jdbc配置文件。 package屬性的name指定了mapper介面和mapper xml文件所在的包,mybatis會掃描這個包,自動註冊mapper介面和mapper xml文件。
創建測試用例Demo4Test
,如下:
package com.javacode2018.chat03.demo4; import com.javacode2018.chat03.demo4.mapper.UserMapper; import com.javacode2018.chat03.demo4.model.UserModel; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 公眾號:路人甲Java,工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活! */ @Slf4j public class Demo4Test { private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "demo4/mybatis-config.xml"; //讀取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //構建SqlSessionFactory對象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.sqlSessionFactory = sqlSessionFactory; } /** * 通過map給Mapper介面的方法傳遞參數 */ @Test public void getByName() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserModel userModel = userMapper.getByName("路人甲Java"); log.info("{}", userModel); } } }
注意上面的getByName
方法,會調用UserMapper介面的getByName
方法通過用戶名查詢用戶資訊,我們運行一下這個方法,輸出如下:
44:55.747 [main] DEBUG c.j.c.d.mapper.UserMapper.getByName - ==> Preparing: SELECT * FROM t_user WHERE name = ? LIMIT 1 44:55.779 [main] DEBUG c.j.c.d.mapper.UserMapper.getByName - ==> Parameters: 路人甲Java(String) 44:55.797 [main] DEBUG c.j.c.d.mapper.UserMapper.getByName - <== Total: 1 44:55.798 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
這個案例中我們新增的幾個文件結構如下:

傳遞一個Map參數
用法
如果我們需要傳遞的參數比較多,參數個數是動態的,那麼我們可以將這些參數放在一個map中,key為參數名稱,value為參數的值。
Mapper介面中可以這麼定義,如:
List<UserModel> getByMap(Map<String,Object> map);
如我們傳遞:
Map<String, Object> map = new HashMap<>(); map.put("id", 1L); map.put("name", "張學友");
對應的mapper xml中可以通過#{map中的key}
可以獲取key在map中對應的value的值作為參數,如:
SELECT * FROM t_user WHERE id=#{id} OR name = #{name}
案例
下面我們通過map傳遞多個參數來按照id或者用戶名進行查詢。
com.javacode2018.chat03.demo4.mapper.UserMapper
中新增一個方法,和上面UserMapper.xml中的對應,如下:
/** * 通過map查詢 * @param map * @return */ List<UserModel> getByMap(Map<String,Object> map);
注意上面的方法由2個參數,參數名稱分別為id、name
,下面我們在對應的mapper xml中寫對應的操作
chat03srcmainresourcescomjavacode2018chat03demo4mapperUserMapper.xml
中新增下面程式碼:
<!-- 通過map查詢 --> <select id="getByMap" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user WHERE id=#{id} OR name = #{name} ]]> </select>
大家注意一下上面的取值我們是使用#{id}取id參數的值,#{name}取name參數的值,下面我們創建測試用例,看看是否可以正常運行?
Demo4Test
中新增下面方法:
/** * 通過map給Mapper介面的方法傳遞參數 */ @Test public void getByName() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserModel userModel = userMapper.getByName("路人甲Java"); log.info("{}", userModel); } }
運行一下上面的這個測試用例,輸出:
01:28.242 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==> Preparing: SELECT * FROM t_user WHERE id=? OR name = ? 01:28.277 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==> Parameters: 1(Long), 張學友(String) 01:28.296 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - <== Total: 2 01:28.297 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1) 01:28.298 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=3, name=張學友, age=56, salary=500000.0, sex=1)
傳遞一個java對象參數
當參數比較多,但是具體有多少個參數我們是確定的時候,我們可以將這些參數放在一個javabean對象中。
如我們想通過userId和userName
查詢,可以定義一個dto
對象,屬性添加對應的get、set方法,如:
@Getter @Setter @ToString @Builder @NoArgsConstructor @AllArgsConstructor public class UserFindDto { private Long userId; private String userName; }
注意上面的get、set方法我們通過lombok自動生成的。
UserMapper中新增一個方法,將UserFindDto
作為參數:
/** * 通過UserFindDto進行查詢 * @param userFindDto * @return */ List<UserModel> getListByUserFindDto(UserFindDto userFindDto);
對應的UserMapper.xml中這麼寫,如下:
<!-- 通過map查詢 --> <select id="getListByUserFindDto" parameterType="com.javacode2018.chat03.demo4.dto.UserFindDto" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user WHERE id=#{userId} OR name = #{userName} ]]> </select>
Demo4Test
中創建一個測試用例來調用一下新增的這個mapper介面中的方法,如下:
@Test public void getListByUserFindDto() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserFindDto userFindDto = UserFindDto.builder().userId(1L).userName("張學友").build(); List<UserModel> userModelList = userMapper.getListByUserFindDto(userFindDto); userModelList.forEach(item -> { log.info("{}", item); }); } }
上面我們通過傳遞一個userFindDto
對象進行查詢,運行輸出:
20:59.454 [main] DEBUG c.j.c.d.m.U.getListByUserFindDto - ==> Preparing: SELECT * FROM t_user WHERE id=? OR name = ? 20:59.487 [main] DEBUG c.j.c.d.m.U.getListByUserFindDto - ==> Parameters: 1(Long), 張學友(String) 20:59.508 [main] DEBUG c.j.c.d.m.U.getListByUserFindDto - <== Total: 2 20:59.509 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1) 20:59.511 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=3, name=張學友, age=56, salary=500000.0, sex=1)
傳遞java對象的方式相對於map的方式更清晰一些,可以明確知道具體有哪些參數,而傳遞map,我們是不知道這個map中具體需要哪些參數的,map對參數也沒有約束,參數可以隨意傳,建議多個參數的情況下選擇通過java對象進行傳參。
傳遞多個參數
上面我們介紹的都是傳遞一個參數,那麼是否可以傳遞多個參數呢?我們來試試吧。
案例
我們來新增一個通過用戶id或用戶名查詢的操作。
com.javacode2018.chat03.demo4.mapper.UserMapper
中新增一個方法,和上面UserMapper.xml中的對應,如下:
/** * 通過id或者name查詢 * * @param id * @param name * @return */ UserModel getByIdOrName(Long id, String name);
注意上面的方法由2個參數,參數名稱分別為id、name
,下面我們在對應的mapper xml中寫對應的操作
chat03srcmainresourcescomjavacode2018chat03demo4mapperUserMapper.xml
中新增下面程式碼:
<!-- 通過id或者name查詢 --> <select id="getByIdOrName" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user WHERE id=#{id} OR name = #{name} LIMIT 1 ]]> </select>
大家注意一下上面的取值我們是使用#{id}取id參數的值,#{name}取name參數的值,下面我們創建測試用例,看看是否可以正常運行?
Demo4Test
中新增下面方法:
/** * 通過map給Mapper介面的方法傳遞參數 */ @Test public void getByIdOrName() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserModel userModel = userMapper.getByIdOrName(1L, "路人甲Java"); log.info("{}", userModel); } }
運行一下上面的這個測試用例,報錯了,我們截取部分主要的錯誤資訊,如下:
org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2] ### Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2] at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)
上面報錯,給大家解釋一下,sql中我們通過#{id}去獲取id參數的值,但是mybatis無法通過#{id}獲取到id的值,所以報錯了,從上錯誤中我們看到有這樣的一段:
Available parameters are [arg1, arg0, param1, param2]
上面表示可用的參數名稱列表,我們可以在sql中通過#{參數名稱}
來引參數列表中的參數,先不說這4個參數具體怎麼來的,那麼我們將sql改成下面這樣試試:
SELECT * FROM t_user WHERE id=#{arg0} OR name = #{arg1} LIMIT 1
再運行一下測試用例,看看效果:
46:07.533 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Preparing: SELECT * FROM t_user WHERE id=? OR name = ? LIMIT 1 46:07.566 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Parameters: 1(Long), 路人甲Java(String) 46:07.585 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - <== Total: 1 46:07.586 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
這下正常了。
我們將sql再修改一下,修改成下面這樣:
SELECT * FROM t_user WHERE id=#{param1} OR name = #{param2} LIMIT 1
運行一下測試用例,輸出:
47:19.935 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Preparing: SELECT * FROM t_user WHERE id=? OR name = ? LIMIT 1 47:19.966 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Parameters: 1(Long), 路人甲Java(String) 47:19.984 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - <== Total: 1 47:19.985 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
也是正常的。
我們來分析一下mybatis對於這種多個參數是如何處理的?
多參數mybatis的處理
mybatis處理多個參數的時候,會將多個參數封裝到一個map中,map的key為參數的名稱,java可以通過反射獲取方法參數的名稱,下面這個方法:
UserModel getByIdOrName(Long id, String name);
編譯之後,方法參數的名稱通過反射獲取的並不是id、name
,而是arg0、arg1
,也就是說編譯之後,方法真實的參數名稱會丟失,會變成arg+參數下標
的格式。
所以上面傳遞的參數相當於傳遞了下面這樣的一個map:
Map<String,Object> map = new HashMap<>(); map.put("arg0",id); map.put("arg1",name);
那麼參數中的param1、param2
又是什麼呢?
上面的map中會放入按照參數名稱->參數的值
的方式將其放入map中,通過反射的方式獲取的參數名稱是可能會發生變化的,我們編譯java程式碼使用javac命令,javac命令有個-parameters
參數,當編譯程式碼的時候加上這個參數,方法的實際名稱會被編譯到class位元組碼文件中,當通過反射獲取方法名稱的時候就不是arg0、arg1
這種格式了,而是真實的參數名稱:id、name
了,我們來修改一下maven的配置讓maven編譯程式碼的時候加上這個參數,修改chat03/pom.xml
中的build元素,這個元素中加入下面程式碼:
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin> </plugins>
idea中編譯程式碼也加一下這個參數,操作如下:
點擊File->Settings->Build,Execution,Deployment->Java Compiler
,如下圖:

下面我們將demo4/UserMapper.xml
中的getByIdOrName
對應的sql修改成下面這樣:
SELECT * FROM t_user WHERE id=#{arg0} OR name = #{arg1} LIMIT 1
使用maven命令重新編譯一下chat03的程式碼,cmd命令中mybatis-series/pom.xml
所在目錄執行下面命令,如下:
D:codeIdeaProjectsmybatis-series>mvn clean compile -pl :chat03 [INFO] Scanning for projects... [INFO] [INFO] ----------------------< com.javacode2018:chat03 >----------------------- [INFO] Building chat03 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ chat03 --- [INFO] Deleting D:codeIdeaProjectsmybatis-serieschat03target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ chat03 --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 1 resource [INFO] Copying 11 resources [INFO] [INFO] --- maven-compiler-plugin:3.3:compile (default-compile) @ chat03 --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 13 source files to D:codeIdeaProjectsmybatis-serieschat03targetclasses [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.532 s [INFO] Finished at: 2019-12-10T13:41:05+08:00 [INFO] ------------------------------------------------------------------------
再運行一下com.javacode2018.chat03.demo4.Demo4Test#getByIdOrName
,輸出如下:
org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [name, id, param1, param2] ### Cause: org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [name, id, param1, param2] at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)
又報錯了,這次錯誤資訊變了,注意有這麼一行:
Parameter 'arg0' not found. Available parameters are [name, id, param1, param2]
參數名稱變成了真實的名稱了,但是還是有param1、param2
,方法參數名稱不管怎麼變,編譯方式如何變化,param1, param2
始終在這裡,這個param1, param2
就是為了應對不同的編譯方式導致參數名稱而發生變化的,mybatis內部除了將參數按照名稱->值
的方式放入map外,還會按照參數的順序放入一些值,這些值的key就是param+參數位置
,這個位置從1開始的,所以id是第一個參數,對應的key是param1
,name對應的key是param2
,value對應的還是參數的值,所以mybatis對於參數的處理相當於下面過程:
Map<String,Object> map = new HashMap<>(); map.put("反射獲取的參數id的名稱",id); map.put("反射獲取的參數name的名稱",name); map.put("param1",id); map.put("param2",name);
我們將demo4/UserMaper.xml
中的getByIdOrName
對應的sql改成param的方式,如下:
SELECT * FROM t_user WHERE id=#{param1} OR name = #{param2} LIMIT 1
再運行一下com.javacode2018.chat03.demo4.Demo4Test#getByIdOrName
,輸出如下,正常了:
51:12.588 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Preparing: SELECT * FROM t_user WHERE id=? OR name = ? LIMIT 1 51:12.619 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Parameters: 1(Long), 路人甲Java(String) 51:12.634 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - <== Total: 1 51:12.635 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
使用注意
- 使用參數名稱的方式對編譯環境有很強的依賴性,如果編譯中加上了`-parameters`參數,參數實際名稱可以直接使用,如果沒有加,參數名稱就變成`arg下標`的格式了,這種很容易出錯
- sql中使用`param1、param2、paramN`這種方式來引用多參數,對參數的順序依賴性特彆強,如果有人把參數的順序調整了或者調整了參數的個數,後果就是災難性的,所以這種方式不建議大家使用。
多參數中用@param指定參數名稱
剛才上面講了多參數傳遞的使用上面,對參數名稱和順序有很強的依賴性,容易導致一些嚴重的錯誤。
mybatis也為我們考慮到了這種情況,可以讓我們自己去指定參數的名稱,通過@param(「參數名稱」)
來給參數指定名稱。
com.javacode2018.chat03.demo4.mapper.UserMapper#getByIdOrName
做一下修改:
/** * 通過id或者name查詢 * * @param id * @param name * @return */ UserModel getByIdOrName(@Param("userId") Long id, @Param("userName") String name);
上面我們通過@Param註解給兩個參數明確指定了名稱,分別是userId、userName
,對應的UserMapper.xml中也做一下調整,如下:
<!-- 通過id或者name查詢 --> <select id="getByIdOrName" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user WHERE id=#{userId} OR name = #{userName} LIMIT 1 ]]> </select>
運行com.javacode2018.chat03.demo4.Demo4Test#getByMap
,輸出:
13:25.431 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==> Preparing: SELECT * FROM t_user WHERE id=? OR name = ? 13:25.460 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==> Parameters: null, 張學友(String) 13:25.477 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - <== Total: 1 13:25.478 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=3, name=張學友, age=56, salary=500000.0, sex=1)
mybatis參數處理相關源碼
上面參數的解析過程程式碼在org.apache.ibatis.reflection.ParamNameResolver
類中,主要看下面的2個方法:
public ParamNameResolver(Configuration config, Method method) public Object getNamedParams(Object[] args)
這2個方法建議大家都設置一下斷點細看一下整個過程,方法的實現不複雜,大家花半個小時去看一下加深一下理解。
下面我們繼續說其他方式的傳參。
傳遞1個Collection參數
當傳遞的參數類型是java.util.Collection
的時候,會被放在map中,key為collection
,value為參數的值,如下面的查詢方法:
/** * 查詢用戶id列表 * * @param idCollection * @return */ List<UserModel> getListByIdCollection(Collection<Long> idCollection);
上面的查詢方法,mybatis內部會將idList
做一下處理:
Map<String,Object> map = new HashMap<>(); map.put("collection",idCollection)
所以我們在mapper xml中使用的使用,需要通過collection
名稱來引用idCollection
參數,如下:
<!-- 通過用戶id列表查詢 --> <select id="getListByIdCollection" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user WHERE id IN (#{collection[0]},#{collection[1]}) ]]> </select>
com.javacode2018.chat03.demo4.Demo4Test
中寫個測試用例getListByIdList
,查詢2個用戶資訊,如下:
@Test public void getListByIdCollection() { log.info("----------"); try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<Long> userIdList = Arrays.asList(1L, 3L); List<UserModel> userModelList = userMapper.getListByIdCollection(userIdList); userModelList.forEach(item -> { log.info("{}", item); }); } }
運行輸出:
26:15.774 [main] INFO c.j.chat03.demo4.Demo4Test - ---------- 26:16.055 [main] DEBUG c.j.c.d.m.U.getListByIdCollection - ==> Preparing: SELECT * FROM t_user WHERE id IN (?,?) 26:16.083 [main] DEBUG c.j.c.d.m.U.getListByIdCollection - ==> Parameters: 1(Long), 3(Long) 26:16.102 [main] DEBUG c.j.c.d.m.U.getListByIdCollection - <== Total: 2 26:16.103 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1) 26:16.105 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=3, name=張學友, age=56, salary=500000.0, sex=1)
Mybatis中集合參數處理了源碼解析
集合參數,mybatis會進行一些特殊處理,程式碼在下面的方法中:
org.apache.ibatis.session.defaults.DefaultSqlSession#wrapCollection
這個方法的源碼如下:
private Object wrapCollection(final Object object) { if (object instanceof Collection) { StrictMap<Object> map = new StrictMap<>(); map.put("collection", object); if (object instanceof List) { map.put("list", object); } return map; } else if (object != null && object.getClass().isArray()) { StrictMap<Object> map = new StrictMap<>(); map.put("array", object); return map; } return object; }
源碼解釋: 判斷參數是否是
java.util.Collection
類型,如果是,會放在map中,key為collection
。 如果參數是java.util.List
類型的,會在map中繼續放一個list
作為key來引用這個對象。 如果參數是數組類型的,會通過array
來引用這個對象。
傳遞1個List參數
從上面源碼中可知,List類型的參數會被放在map中,可以通過2個key(collection
和list
)都可以引用到這個List對象。
com.javacode2018.chat03.demo4.mapper.UserMapper
中新增一個方法:
/** * 查詢用戶id列表 * * @param idList * @return */ List<UserModel> getListByIdList(List<Long> idList);
對應的demo4/UserMaper.xml
中增加一個操作,如下:
<!-- 通過用戶id列表查詢 --> <select id="getListByIdList" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user WHERE id IN (#{list[0]},#{collection[1]}) ]]> </select>
注意上面我們使用了2中方式獲取參數,通過
list、collection
都可以引用List類型的參數。
新增一個測試用例com.javacode2018.chat03.demo4.Demo4Test#getListByIdList
,如下:
@Test public void getListByIdList() { log.info("----------"); try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<Long> userIdList = Arrays.asList(1L, 3L); List<UserModel> userModelList = userMapper.getListByIdList(userIdList); userModelList.forEach(item -> { log.info("{}", item); }); } }
運行輸出:
33:17.871 [main] INFO c.j.chat03.demo4.Demo4Test - ---------- 33:18.153 [main] DEBUG c.j.c.d.m.UserMapper.getListByIdList - ==> Preparing: SELECT * FROM t_user WHERE id IN (?,?) 33:18.185 [main] DEBUG c.j.c.d.m.UserMapper.getListByIdList - ==> Parameters: 1(Long), 3(Long) 33:18.207 [main] DEBUG c.j.c.d.m.UserMapper.getListByIdList - <== Total: 2 33:18.208 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1) 33:18.210 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=3, name=張學友, age=56, salary=500000.0, sex=1)
傳遞1個數組參數
數組類型的參數從上面源碼中可知,sql中需要通過array
來進行引用,這個就不寫了,案例中也是有的,大家可以去看一下com.javacode2018.chat03.demo4.Demo4Test#getListByIdArray
這個方法。
ResultHandler作為參數
用法
查詢的數量比較大的時候,返回一個List集合佔用的記憶體還是比較多的,比如我們想導出很多數據,實際上如果我們通過jdbc的方式,遍歷ResultSet
的next
方法,一條條處理,而不用將其存到List集合中再取處理。
mybatis中也支援我們這麼做,可以使用ResultHandler
對象,猶如其名,這個介面是用來處理結果的,先看一下其定義:
public interface ResultHandler<T> { void handleResult(ResultContext<? extends T> resultContext); }
裡面有1個方法,方法的參數是ResultContext
類型的,這個也是一個介面,看一下源碼:
public interface ResultContext<T> { T getResultObject(); int getResultCount(); boolean isStopped(); void stop(); }
4個方法:
- getResultObject:獲取當前行的結果
- getResultCount:獲取當前結果到第幾行了
- isStopped:判斷是否需要停止遍歷結果集
- stop:停止遍歷結果集
ResultContext
介面有一個實現類org.apache.ibatis.executor.result.DefaultResultContext
,mybatis中默認會使用這個類。
案例
我們遍歷t_user
表的所有記錄,第2條遍歷結束之後,停止遍歷,實現如下:
新增一個方法com.javacode2018.chat03.demo4.mapper.UserMapper#getList
,如下:
void getList(ResultHandler<UserModel> resultHandler);
對應的UserMapper.xml
新增sql操作,如下:
<select id="getList" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user ]]> </select>
新增測試用例com.javacode2018.chat03.demo4.Demo4Test#getList
,如下:
@Test public void getList() { log.info("----------"); try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.getList(context -> { //將context參數轉換為DefaultResultContext對象 DefaultResultContext<UserModel> defaultResultContext = (DefaultResultContext<UserModel>) context; log.info("{}", defaultResultContext.getResultObject()); //遍歷到第二條之後停止 if (defaultResultContext.getResultCount() == 2) { //調用stop方法停止遍歷,stop方法會更新內部的一個標誌,置為停止遍歷 defaultResultContext.stop(); } }); } }
運行輸出:
07:05.561 [main] INFO c.j.chat03.demo4.Demo4Test - ---------- 07:05.816 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Preparing: SELECT * FROM t_user 07:05.845 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Parameters: 07:05.864 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1) 07:05.867 [main] INFO c.j.chat03.demo4.Demo4Test - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
本文的內容希望大家都能掌握。
路人甲Java:工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!