Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
MyBatis教程
-> 详解传参的几种方式、原理、源码解析
1、MyBatis未出世之前我们那些痛苦的经历
2、MyBatis入门,你确定mybatis你玩的很溜?
3、MyBatis使用详解(-)
4、MyBatis使用详解(2)
5、详解传参的几种方式、原理、源码解析
6、增删改知识点汇总及主键获取3种方式详解
7、种查询详解
8、自动映射,使用需谨慎
9、延迟加载、鉴别器、继承
10、动态SQL,这么多种你都会么?
11、类型处理器
12、掌握缓存为查询提速!
上一篇:MyBatis使用详解(2)
下一篇:增删改知识点汇总及主键获取3种方式详解
<div style="display:none"></div> ## Mybatis系列第5篇:详解传参的几种方式、原理、源码解析 ### 主要内容 本篇详解mapper接口传参的各种方式。 - 传递一个参数 - 传递一个Map参数 - 传递一个javabean参数 - 多参数中用@param指定参数名称 - java编译中参数名称的处理 - mapper接口传参源码分析 - 传递1个Collection参数 - 传递1个List参数 - 传递1个数组参数 - mybatis对于集合处理源码分析 - ResultHandler作为参数的用法 **本篇文章的案例在上一篇`chat03模块`上进行开发,大家可以到文章的尾部获取整个mybatis系列的案例源码。** **mybatis系列的文章前后都是有依赖的,请大家按顺序去看,尽量不要跳着去看,这样不会出现看不懂的情况,建议大家系统化的学习知识,基础打牢,慢慢才能成为高手。** 使用mybatis开发项目的中,基本上都是使用mapper接口的方式来执行db操作,下面我们来看一下mapper接口传递参数的几种方式及需要注意的地方。 ### 传递一个参数 #### 用法 Mapper接口方法中只有一个参数,如: ```java UserModel getByName(String name); ``` Mapper xml引用这个name参数: ```java #{任意合法名称} ``` 如:`#{name}、#{val}、${x}等等写法都可以引用上面name参数的值`。 #### 案例 创建`UserModel`类,如下: ```java 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`,如下: ```java 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-series\chat03\src\main\resources\com\javacode2018\chat03\demo4\mapper`目录创建`UserMapper.xml`,如下: ```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-series\chat03\src\main\resources`目录创建`jdbc.properties`,如下: ```java 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-series\chat03\src\main\resources\demo4`目录创建`mybatis-config.xml`,如下: ```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`,如下: ```java 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`方法通过用户名查询用户信息,我们运行一下这个方法,输出如下: ```java 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) ``` 这个案例中我们新增的几个文件结构如下: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/76/3fb18153-9618-4b19-a0e9-27d6f7e1a3f6.png) ### 传递一个Map参数 #### 用法 如果我们需要传递的参数比较多,参数个数是动态的,那么我们可以将这些参数放在一个map中,key为参数名称,value为参数的值。 Mapper接口中可以这么定义,如: ```java List<UserModel> getByMap(Map<String,Object> map); ``` 如我们传递: ```java Map<String, Object> map = new HashMap<>(); map.put("id", 1L); map.put("name", "张学友"); ``` 对应的mapper xml中可以通过`#{map中的key}`可以获取key在map中对应的value的值作为参数,如: ```java SELECT * FROM t_user WHERE id=#{id} OR name = #{name} ``` #### 案例 下面我们通过map传递多个参数来按照id或者用户名进行查询。 `com.javacode2018.chat03.demo4.mapper.UserMapper`中新增一个方法,和上面UserMapper.xml中的对应,如下: ```java /** * 通过map查询 * @param map * @return */ List<UserModel> getByMap(Map<String,Object> map); ``` 注意上面的方法由2个参数,参数名称分别为`id、name`,下面我们在对应的mapper xml中写对应的操作 `chat03\src\main\resources\com\javacode2018\chat03\demo4\mapper\UserMapper.xml`中新增下面代码: ```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`中新增下面方法: ```java /** * 通过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); } } ``` 运行一下上面的这个测试用例,输出: ```java 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方法,如: ```java @Getter @Setter @ToString @Builder @NoArgsConstructor @AllArgsConstructor public class UserFindDto { private Long userId; private String userName; } ``` > 注意上面的get、set方法我们通过lombok自动生成的。 UserMapper中新增一个方法,将`UserFindDto`作为参数: ```java /** * 通过UserFindDto进行查询 * @param userFindDto * @return */ List<UserModel> getListByUserFindDto(UserFindDto userFindDto); ``` 对应的UserMapper.xml中这么写,如下: ```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接口中的方法,如下: ```java @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`对象进行查询,运行输出: ```java 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中的对应,如下: ```java /** * 通过id或者name查询 * * @param id * @param name * @return */ UserModel getByIdOrName(Long id, String name); ``` 注意上面的方法由2个参数,参数名称分别为`id、name`,下面我们在对应的mapper xml中写对应的操作 `chat03\src\main\resources\com\javacode2018\chat03\demo4\mapper\UserMapper.xml`中新增下面代码: ```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`中新增下面方法: ```java /** * 通过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); } } ``` 运行一下上面的这个测试用例,报错了,我们截取部分主要的错误信息,如下: ```java 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的值,所以报错了,从上错误中我们看到有这样的一段: ```java Available parameters are [arg1, arg0, param1, param2] ``` 上面表示可用的参数名称列表,我们可以在sql中通过`#{参数名称}`来引参数列表中的参数,先不说这4个参数具体怎么来的,那么我们将sql改成下面这样试试: ```java SELECT * FROM t_user WHERE id=#{arg0} OR name = #{arg1} LIMIT 1 ``` 再运行一下测试用例,看看效果: ```java 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再修改一下,修改成下面这样: ```java SELECT * FROM t_user WHERE id=#{param1} OR name = #{param2} LIMIT 1 ``` 运行一下测试用例,输出: ```java 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可以通过反射获取方法参数的名称,下面这个方法: ```java UserModel getByIdOrName(Long id, String name); ``` 编译之后,方法参数的名称通过反射获取的并不是`id、name`,而是`arg0、arg1`,也就是说编译之后,方法真实的参数名称会丢失,会变成`arg+参数下标`的格式。 所以上面传递的参数相当于传递了下面这样的一个map: ```java 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元素,这个元素中加入下面代码: ```xml <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`,如下图: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/76/a5d50ff4-d511-4f75-ac4a-45f6d4af7b1c.png) 下面我们将`demo4/UserMapper.xml`中的`getByIdOrName`对应的sql修改成下面这样: ```java SELECT * FROM t_user WHERE id=#{arg0} OR name = #{arg1} LIMIT 1 ``` 使用maven命令重新编译一下chat03的代码,cmd命令中`mybatis-series/pom.xml`所在目录执行下面命令,如下: ```java D:\code\IdeaProjects\mybatis-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:\code\IdeaProjects\mybatis-series\chat03\target [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:\code\IdeaProjects\mybatis-series\chat03\target\classes [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`,输出如下: ```java 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) ``` 又报错了,这次错误信息变了,注意有这么一行: ```java 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对于参数的处理相当于下面过程: ```java 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的方式,如下: ```java SELECT * FROM t_user WHERE id=#{param1} OR name = #{param2} LIMIT 1 ``` 再运行一下`com.javacode2018.chat03.demo4.Demo4Test#getByIdOrName`,输出如下,正常了: ```java 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) ``` #### 使用注意 1. **使用参数名称的方式对编译环境有很强的依赖性,如果编译中加上了`-parameters`参数,参数实际名称可以直接使用,如果没有加,参数名称就变成`arg下标`的格式了,这种很容易出错** 2. **sql中使用`param1、param2、paramN`这种方式来引用多参数,对参数的顺序依赖性特别强,如果有人把参数的顺序调整了或者调整了参数的个数,后果就是灾难性的,所以这种方式不建议大家使用。** ### 多参数中用@param指定参数名称 刚才上面讲了多参数传递的使用上面,对参数名称和顺序有很强的依赖性,容易导致一些严重的错误。 mybatis也为我们考虑到了这种情况,可以让我们自己去指定参数的名称,通过`@param(“参数名称”)`来给参数指定名称。 `com.javacode2018.chat03.demo4.mapper.UserMapper#getByIdOrName`做一下修改: ```java /** * 通过id或者name查询 * * @param id * @param name * @return */ UserModel getByIdOrName(@Param("userId") Long id, @Param("userName") String name); ``` 上面我们通过@Param注解给两个参数明确指定了名称,分别是`userId、userName`,对应的UserMapper.xml中也做一下调整,如下: ```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`,输出: ```java 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个方法: ```java public ParamNameResolver(Configuration config, Method method) public Object getNamedParams(Object[] args) ``` 这2个方法建议大家都设置一下断点细看一下整个过程,方法的实现不复杂,大家花半个小时去看一下加深一下理解。 下面我们继续说其他方式的传参。 ### 传递1个Collection参数 当传递的参数类型是`java.util.Collection`的时候,会被放在map中,key为`collection`,value为参数的值,如下面的查询方法: ```java /** * 查询用户id列表 * * @param idCollection * @return */ List<UserModel> getListByIdCollection(Collection<Long> idCollection); ``` 上面的查询方法,mybatis内部会将`idList`做一下处理: ```java Map<String,Object> map = new HashMap<>(); map.put("collection",idCollection) ``` 所以我们在mapper xml中使用的使用,需要通过`collection`名称来引用`idCollection`参数,如下: ```xml <!-- 通过用户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个用户信息,如下: ```java @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); }); } } ``` 运行输出: ```java 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会进行一些特殊处理,代码在下面的方法中: ```java org.apache.ibatis.session.defaults.DefaultSqlSession#wrapCollection ``` 这个方法的源码如下: ```java 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`中新增一个方法: ```java /** * 查询用户id列表 * * @param idList * @return */ List<UserModel> getListByIdList(List<Long> idList); ``` 对应的`demo4/UserMaper.xml`中增加一个操作,如下: ```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`,如下: ```java @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); }); } } ``` 运行输出: ```java 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`对象,犹如其名,这个接口是用来处理结果的,先看一下其定义: ```java public interface ResultHandler<T> { void handleResult(ResultContext<? extends T> resultContext); } ``` 里面有1个方法,方法的参数是`ResultContext`类型的,这个也是一个接口,看一下源码: ```java 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`,如下: ```java void getList(ResultHandler<UserModel> resultHandler); ``` 对应的`UserMapper.xml`新增sql操作,如下: ```xml <select id="getList" resultType="com.javacode2018.chat03.demo4.model.UserModel"> <![CDATA[ SELECT * FROM t_user ]]> </select> ``` 新增测试用例`com.javacode2018.chat03.demo4.Demo4Test#getList`,如下: ```java @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(); } }); } } ``` 运行输出: ```java 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 链接:https://pan.baidu.com/s/1vt-MAX3oJOu9gyxZAhKkbg 提取码:i8op ``` <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container