Mybatis系列第5篇:Mapper接口多种方式传参详解、原理、源码解析

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类,如下:

  1. package com.javacode2018.chat03.demo4.model;
  2. import lombok.*;
  3. /**
  4.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  5.  */
  6. @Getter
  7. @Setter
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. @Builder
  11. @ToString
  12. public class UserModel {
  13.     private Long id;
  14.     private String name;
  15.     private Integer age;
  16.     private Double salary;
  17.     private Integer sex;
  18. }

创建Mapper接口UserMapper,如下:

  1. package com.javacode2018.chat03.demo4.mapper;
  2. import com.javacode2018.chat03.demo4.model.UserModel;
  3. import java.util.List;
  4. import java.util.Map;
  5. /**
  6.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  7.  */
  8. public interface UserMapper {
  9.     /**
  10.      * 通过name查询
  11.      *
  12.      * @param name
  13.      * @return
  14.      */
  15.     UserModel getByName(String name);
  16. }

注意上面有个getByName方法,这个方法传递一个参数。

创建Mapper xml文件UserMapper.xml,mybatis-series\chat03\src\main\resources\com\javacode2018\chat03\demo4\mapper目录创建UserMapper.xml,如下:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  3.         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4. <mapper namespace="com.javacode2018.chat03.demo4.mapper.UserMapper">
  5.     <!-- 通过name查询 -->
  6.     <select id="getByName" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  7.         <![CDATA[
  8.         SELECT * FROM t_user WHERE name = #{value} LIMIT 1
  9.         ]]>
  10.     </select>
  11. </mapper>

上面有个getByName通过用户名查询,通过#{value}引用传递进来的name参数,当一个参数的时候#{变量名称}中变量名称可以随意写,都可以取到传入的参数。

创建属性配置文件,mybatis-series\chat03\src\main\resources目录创建jdbc.properties,如下:

  1. jdbc.driver=com.mysql.jdbc.Driver
  2. jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
  3. jdbc.username=root
  4. jdbc.password=root123

上面是我本地db配置,大家可以根据自己db信息做对应修改。

创建mybatis全局配置文件,mybatis-series\chat03\src\main\resources\demo4目录创建mybatis-config.xml,如下:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3.         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6.     <!-- 引入外部jdbc配置 -->
  7.     <properties resource="jdbc.properties"/>
  8.     <!-- 环境配置,可以配置多个环境 -->
  9.     <environments default="demo4">
  10.         <environment id="demo4">
  11.             <!-- 事务管理器工厂配置 -->
  12.             <transactionManager type="JDBC"/>
  13.             <!-- 数据源工厂配置,使用工厂来创建数据源 -->
  14.             <dataSource type="POOLED">
  15.                 <property name="driver" value="${jdbc.driver}"/>
  16.                 <property name="url" value="${jdbc.url}"/>
  17.                 <property name="username" value="${jdbc.username}"/>
  18.                 <property name="password" value="${jdbc.password}"/>
  19.             </dataSource>
  20.         </environment>
  21.     </environments>
  22.     <mappers>
  23.         <package name="com.javacode2018.chat03.demo4.mapper"/>
  24.     </mappers>
  25. </configuration>

上面通过properties的resource属性引入了jdbc配置文件。

package属性的name指定了mapper接口和mapper xml文件所在的包,mybatis会扫描这个包,自动注册mapper接口和mapper xml文件。

创建测试用例Demo4Test,如下:

  1. package com.javacode2018.chat03.demo4;
  2. import com.javacode2018.chat03.demo4.mapper.UserMapper;
  3. import com.javacode2018.chat03.demo4.model.UserModel;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.apache.ibatis.io.Resources;
  6. import org.apache.ibatis.session.SqlSession;
  7. import org.apache.ibatis.session.SqlSessionFactory;
  8. import org.apache.ibatis.session.SqlSessionFactoryBuilder;
  9. import org.junit.Before;
  10. import org.junit.Test;
  11. import java.io.IOException;
  12. import java.io.InputStream;
  13. import java.util.HashMap;
  14. import java.util.List;
  15. import java.util.Map;
  16. /**
  17.  * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  18.  */
  19. @Slf4j
  20. public class Demo4Test {
  21.     private SqlSessionFactory sqlSessionFactory;
  22.     @Before
  23.     public void before() throws IOException {
  24.         //指定mybatis全局配置文件
  25.         String resource = "demo4/mybatis-config.xml";
  26.         //读取全局配置文件
  27.         InputStream inputStream = Resources.getResourceAsStream(resource);
  28.         //构建SqlSessionFactory对象
  29.         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  30.         this.sqlSessionFactory = sqlSessionFactory;
  31.     }
  32.     /**
  33.      * 通过map给Mapper接口的方法传递参数
  34.      */
  35.     @Test
  36.     public void getByName() {
  37.         try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  38.             UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  39.             UserModel userModel = userMapper.getByName("路人甲Java");
  40.             log.info("{}", userModel);
  41.         }
  42.     }
  43. }

注意上面的getByName方法,会调用UserMapper接口的getByName方法通过用户名查询用户信息,我们运行一下这个方法,输出如下:

  1. 44:55.747 [main] DEBUG c.j.c.d.mapper.UserMapper.getByName - ==>  Preparing: SELECT * FROM t_user WHERE name = ? LIMIT 1 
  2. 44:55.779 [main] DEBUG c.j.c.d.mapper.UserMapper.getByName - ==> Parameters: 路人甲Java(String)
  3. 44:55.797 [main] DEBUG c.j.c.d.mapper.UserMapper.getByName - <==      Total: 1
  4. 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);

如我们传递:

  1. Map<String, Object> map = new HashMap<>();
  2.             map.put("id"1L);
  3.             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中的对应,如下:

  1. /**
  2.  * 通过map查询
  3.  * @param map
  4.  * @return
  5.  */
  6. List<UserModel> getByMap(Map<String,Object> map);

注意上面的方法由2个参数,参数名称分别为id、name,下面我们在对应的mapper xml中写对应的操作

chat03\src\main\resources\com\javacode2018\chat03\demo4\mapper\UserMapper.xml中新增下面代码:

  1. <!-- 通过map查询 -->
  2. <select id="getByMap" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  3.     <![CDATA[
  4.     SELECT * FROM t_user WHERE id=#{id} OR name = #{name}
  5.     ]]>
  6. </select>

大家注意一下上面的取值我们是使用#{id}取id参数的值,#{name}取name参数的值,下面我们创建测试用例,看看是否可以正常运行?

Demo4Test中新增下面方法:

  1. /**
  2.  * 通过map给Mapper接口的方法传递参数
  3.  */
  4. @Test
  5. public void getByName() {
  6.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  7.         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  8.         UserModel userModel = userMapper.getByName("路人甲Java");
  9.         log.info("{}", userModel);
  10.     }
  11. }

运行一下上面的这个测试用例,输出:

  1. 01:28.242 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==>  Preparing: SELECT * FROM t_user WHERE id=? OR name = ? 
  2. 01:28.277 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==> Parameters: 1(Long), 张学友(String)
  3. 01:28.296 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - <==      Total: 2
  4. 01:28.297 [main] INFO  c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
  5. 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方法,如:

  1. @Getter
  2. @Setter
  3. @ToString
  4. @Builder
  5. @NoArgsConstructor
  6. @AllArgsConstructor
  7. public class UserFindDto {
  8.     private Long userId;
  9.     private String userName;
  10. }

注意上面的get、set方法我们通过lombok自动生成的。

UserMapper中新增一个方法,将UserFindDto作为参数:

  1. /**
  2.  * 通过UserFindDto进行查询
  3.  * @param userFindDto
  4.  * @return
  5.  */
  6. List<UserModel> getListByUserFindDto(UserFindDto userFindDto);

对应的UserMapper.xml中这么写,如下:

  1. <!-- 通过map查询 -->
  2. <select id="getListByUserFindDto" parameterType="com.javacode2018.chat03.demo4.dto.UserFindDto" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  3.     <![CDATA[
  4.     SELECT * FROM t_user WHERE id=#{userId} OR name = #{userName}
  5.     ]]>
  6. </select>

Demo4Test中创建一个测试用例来调用一下新增的这个mapper接口中的方法,如下:

  1. @Test
  2. public void getListByUserFindDto() {
  3.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  4.         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  5.         UserFindDto userFindDto = UserFindDto.builder().userId(1L).userName("张学友").build();
  6.         List<UserModel> userModelList = userMapper.getListByUserFindDto(userFindDto);
  7.         userModelList.forEach(item -> {
  8.             log.info("{}", item);
  9.         });
  10.     }
  11. }

上面我们通过传递一个userFindDto对象进行查询,运行输出:

  1. 20:59.454 [main] DEBUG c.j.c.d.m.U.getListByUserFindDto - ==>  Preparing: SELECT * FROM t_user WHERE id=? OR name = ? 
  2. 20:59.487 [main] DEBUG c.j.c.d.m.U.getListByUserFindDto - ==> Parameters: 1(Long), 张学友(String)
  3. 20:59.508 [main] DEBUG c.j.c.d.m.U.getListByUserFindDto - <==      Total: 2
  4. 20:59.509 [main] INFO  c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
  5. 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中的对应,如下:

  1. /**
  2.  * 通过id或者name查询
  3.  *
  4.  * @param id
  5.  * @param name
  6.  * @return
  7.  */
  8. UserModel getByIdOrName(Long id, String name);

注意上面的方法由2个参数,参数名称分别为id、name,下面我们在对应的mapper xml中写对应的操作

chat03\src\main\resources\com\javacode2018\chat03\demo4\mapper\UserMapper.xml中新增下面代码:

  1. <!-- 通过id或者name查询 -->
  2. <select id="getByIdOrName" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  3.     <![CDATA[
  4.     SELECT * FROM t_user WHERE id=#{id} OR name = #{name} LIMIT 1
  5.     ]]>
  6. </select>

大家注意一下上面的取值我们是使用#{id}取id参数的值,#{name}取name参数的值,下面我们创建测试用例,看看是否可以正常运行?

Demo4Test中新增下面方法:

  1. /**
  2.  * 通过map给Mapper接口的方法传递参数
  3.  */
  4. @Test
  5. public void getByIdOrName() {
  6.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  7.         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  8.         UserModel userModel = userMapper.getByIdOrName(1L, "路人甲Java");
  9.         log.info("{}", userModel);
  10.     }
  11. }

运行一下上面的这个测试用例,报错了,我们截取部分主要的错误信息,如下:

  1. org.apache.ibatis.exceptions.PersistenceException: 
  2. ### Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
  3. ### Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
  4.     at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
  5.     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)
  6.     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
  7.     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

再运行一下测试用例,看看效果:

  1. 46:07.533 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==>  Preparing: SELECT * FROM t_user WHERE id=? OR name = ? LIMIT 1 
  2. 46:07.566 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Parameters: 1(Long), 路人甲Java(String)
  3. 46:07.585 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - <==      Total: 1
  4. 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

运行一下测试用例,输出:

  1. 47:19.935 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==>  Preparing: SELECT * FROM t_user WHERE id=? OR name = ? LIMIT 1 
  2. 47:19.966 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Parameters: 1(Long), 路人甲Java(String)
  3. 47:19.984 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - <==      Total: 1
  4. 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:

  1. Map<String,Object> map = new HashMap<>();
  2. map.put("arg0",id);
  3. map.put("arg1",name);

那么参数中的param1、param2又是什么呢?

上面的map中会放入按照参数名称->参数的值的方式将其放入map中,通过反射的方式获取的参数名称是可能会发生变化的,我们编译java代码使用javac命令,javac命令有个-parameters参数,当编译代码的时候加上这个参数,方法的实际名称会被编译到class字节码文件中,当通过反射获取方法名称的时候就不是arg0、arg1这种格式了,而是真实的参数名称:id、name了,我们来修改一下maven的配置让maven编译代码的时候加上这个参数,修改chat03/pom.xml中的build元素,这个元素中加入下面代码:

  1. <plugins>
  2.     <plugin>
  3.         <groupId>org.apache.maven.plugins</groupId>
  4.         <artifactId>maven-compiler-plugin</artifactId>
  5.         <version>3.3</version>
  6.         <configuration>
  7.             <compilerArgs>
  8.                 <arg>-parameters</arg>
  9.             </compilerArgs>
  10.         </configuration>
  11.     </plugin>
  12. </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所在目录执行下面命令,如下:

  1. D:\code\IdeaProjects\mybatis-series>mvn clean compile -pl :chat03
  2. [INFO] Scanning for projects...
  3. [INFO]
  4. [INFO] ----------------------< com.javacode2018:chat03 >-----------------------
  5. [INFO] Building chat03 1.0-SNAPSHOT
  6. [INFO] --------------------------------[ jar ]---------------------------------
  7. [INFO]
  8. [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ chat03 ---
  9. [INFO] Deleting D:\code\IdeaProjects\mybatis-series\chat03\target
  10. [INFO]
  11. [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ chat03 ---
  12. [INFO] Using 'UTF-8' encoding to copy filtered resources.
  13. [INFO] Copying 1 resource
  14. [INFO] Copying 11 resources
  15. [INFO]
  16. [INFO] --- maven-compiler-plugin:3.3:compile (default-compile) @ chat03 ---
  17. [INFO] Changes detected - recompiling the module!
  18. [INFO] Compiling 13 source files to D:\code\IdeaProjects\mybatis-series\chat03\target\classes
  19. [INFO] ------------------------------------------------------------------------
  20. [INFO] BUILD SUCCESS
  21. [INFO] ------------------------------------------------------------------------
  22. [INFO] Total time:  3.532 s
  23. [INFO] Finished at: 2019-12-10T13:41:05+08:00
  24. [INFO] ------------------------------------------------------------------------

再运行一下com.javacode2018.chat03.demo4.Demo4Test#getByIdOrName,输出如下:

  1. org.apache.ibatis.exceptions.PersistenceException: 
  2. ### Error querying database.  Cause: org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [name, id, param1, param2]
  3. ### Cause: org.apache.ibatis.binding.BindingException: Parameter 'arg0' not found. Available parameters are [name, id, param1, param2]
  4.     at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
  5.     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149)
  6.     at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
  7.     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对于参数的处理相当于下面过程:

  1. Map<String,Object> map = new HashMap<>();
  2. map.put("反射获取的参数id的名称",id);
  3. map.put("反射获取的参数name的名称",name);
  4. map.put("param1",id);
  5. 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,输出如下,正常了:

  1. 51:12.588 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==>  Preparing: SELECT * FROM t_user WHERE id=? OR name = ? LIMIT 1 
  2. 51:12.619 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - ==> Parameters: 1(Long), 路人甲Java(String)
  3. 51:12.634 [main] DEBUG c.j.c.d.m.UserMapper.getByIdOrName - <==      Total: 1
  4. 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做一下修改:

  1. /**
  2.  * 通过id或者name查询
  3.  *
  4.  * @param id
  5.  * @param name
  6.  * @return
  7.  */
  8. UserModel getByIdOrName(@Param("userId") Long id, @Param("userName") String name);

上面我们通过@Param注解给两个参数明确指定了名称,分别是userId、userName,对应的UserMapper.xml中也做一下调整,如下:

  1. <!-- 通过id或者name查询 -->
  2. <select id="getByIdOrName" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  3.     <![CDATA[
  4.     SELECT * FROM t_user WHERE id=#{userId} OR name = #{userName} LIMIT 1
  5.     ]]>
  6. </select>

运行com.javacode2018.chat03.demo4.Demo4Test#getByMap,输出:

  1. 13:25.431 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==>  Preparing: SELECT * FROM t_user WHERE id=? OR name = ? 
  2. 13:25.460 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - ==> Parameters: null, 张学友(String)
  3. 13:25.477 [main] DEBUG c.j.c.d.mapper.UserMapper.getByMap - <==      Total: 1
  4. 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个方法:

  1. public ParamNameResolver(Configuration config, Method method)
  2. public Object getNamedParams(Object[] args)

这2个方法建议大家都设置一下断点细看一下整个过程,方法的实现不复杂,大家花半个小时去看一下加深一下理解。

下面我们继续说其他方式的传参。

传递1个Collection参数

当传递的参数类型是java.util.Collection的时候,会被放在map中,key为collection,value为参数的值,如下面的查询方法:

  1. /**
  2.  * 查询用户id列表
  3.  *
  4.  * @param idCollection
  5.  * @return
  6.  */
  7. List<UserModel> getListByIdCollection(Collection<Long> idCollection);

上面的查询方法,mybatis内部会将idList做一下处理:

  1. Map<String,Object> map = new HashMap<>();
  2. map.put("collection",idCollection)

所以我们在mapper xml中使用的使用,需要通过collection名称来引用idCollection参数,如下:

  1. <!-- 通过用户id列表查询 -->
  2. <select id="getListByIdCollection" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  3.     <![CDATA[
  4.     SELECT * FROM t_user WHERE id IN (#{collection[0]},#{collection[1]})
  5.     ]]>
  6. </select>

com.javacode2018.chat03.demo4.Demo4Test中写个测试用例getListByIdList,查询2个用户信息,如下:

  1. @Test
  2. public void getListByIdCollection() {
  3.     log.info("----------");
  4.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  5.         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  6.         List<Long> userIdList = Arrays.asList(1L, 3L);
  7.         List<UserModel> userModelList = userMapper.getListByIdCollection(userIdList);
  8.         userModelList.forEach(item -> {
  9.             log.info("{}", item);
  10.         });
  11.     }
  12. }

运行输出:

  1. 26:15.774 [main] INFO  c.j.chat03.demo4.Demo4Test - ----------
  2. 26:16.055 [main] DEBUG c.j.c.d.m.U.getListByIdCollection - ==>  Preparing: SELECT * FROM t_user WHERE id IN (?,?) 
  3. 26:16.083 [main] DEBUG c.j.c.d.m.U.getListByIdCollection - ==> Parameters: 1(Long), 3(Long)
  4. 26:16.102 [main] DEBUG c.j.c.d.m.U.getListByIdCollection - <==      Total: 2
  5. 26:16.103 [main] INFO  c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
  6. 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

这个方法的源码如下:

  1. private Object wrapCollection(final Object object) {
  2.     if (object instanceof Collection) {
  3.       StrictMap<Object> map = new StrictMap<>();
  4.       map.put("collection", object);
  5.       if (object instanceof List) {
  6.         map.put("list", object);
  7.       }
  8.       return map;
  9.     } else if (object != null && object.getClass().isArray()) {
  10.       StrictMap<Object> map = new StrictMap<>();
  11.       map.put("array", object);
  12.       return map;
  13.     }
  14.     return object;
  15.   }

源码解释:

判断参数是否是java.util.Collection类型,如果是,会放在map中,key为collection

如果参数是java.util.List类型的,会在map中继续放一个list作为key来引用这个对象。

如果参数是数组类型的,会通过array来引用这个对象。

传递1个List参数

从上面源码中可知,List类型的参数会被放在map中,可以通过2个key(collectionlist)都可以引用到这个List对象。

com.javacode2018.chat03.demo4.mapper.UserMapper中新增一个方法:

  1. /**
  2.  * 查询用户id列表
  3.  *
  4.  * @param idList
  5.  * @return
  6.  */
  7. List<UserModel> getListByIdList(List<Long> idList);

对应的demo4/UserMaper.xml中增加一个操作,如下:

  1. <!-- 通过用户id列表查询 -->
  2. <select id="getListByIdList" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  3.     <![CDATA[
  4.     SELECT * FROM t_user WHERE id IN (#{list[0]},#{collection[1]})
  5.     ]]>
  6. </select>

注意上面我们使用了2中方式获取参数,通过list、collection都可以引用List类型的参数。

新增一个测试用例com.javacode2018.chat03.demo4.Demo4Test#getListByIdList,如下:

  1. @Test
  2. public void getListByIdList() {
  3.     log.info("----------");
  4.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  5.         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  6.         List<Long> userIdList = Arrays.asList(1L, 3L);
  7.         List<UserModel> userModelList = userMapper.getListByIdList(userIdList);
  8.         userModelList.forEach(item -> {
  9.             log.info("{}", item);
  10.         });
  11.     }
  12. }

运行输出:

  1. 33:17.871 [main] INFO  c.j.chat03.demo4.Demo4Test - ----------
  2. 33:18.153 [main] DEBUG c.j.c.d.m.UserMapper.getListByIdList - ==>  Preparing: SELECT * FROM t_user WHERE id IN (?,?) 
  3. 33:18.185 [main] DEBUG c.j.c.d.m.UserMapper.getListByIdList - ==> Parameters: 1(Long), 3(Long)
  4. 33:18.207 [main] DEBUG c.j.c.d.m.UserMapper.getListByIdList - <==      Total: 2
  5. 33:18.208 [main] INFO  c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
  6. 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的方式,遍历ResultSetnext方法,一条条处理,而不用将其存到List集合中再取处理。

mybatis中也支持我们这么做,可以使用ResultHandler对象,犹如其名,这个接口是用来处理结果的,先看一下其定义:

  1. public interface ResultHandler<T> {
  2.   void handleResult(ResultContext<? extends T> resultContext);
  3. }

里面有1个方法,方法的参数是ResultContext类型的,这个也是一个接口,看一下源码:

  1. public interface ResultContext<T> {
  2.   T getResultObject();
  3.   int getResultCount();
  4.   boolean isStopped();
  5.   void stop();
  6. }

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操作,如下:

  1. <select id="getList" resultType="com.javacode2018.chat03.demo4.model.UserModel">
  2.     <![CDATA[
  3.     SELECT * FROM t_user
  4.     ]]>
  5. </select>

新增测试用例com.javacode2018.chat03.demo4.Demo4Test#getList,如下:

  1. @Test
  2. public void getList() {
  3.     log.info("----------");
  4.     try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
  5.         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  6.         userMapper.getList(context -> {
  7.             //将context参数转换为DefaultResultContext对象
  8.             DefaultResultContext<UserModel> defaultResultContext = (DefaultResultContext<UserModel>) context;
  9.             log.info("{}", defaultResultContext.getResultObject());
  10.             //遍历到第二条之后停止
  11.             if (defaultResultContext.getResultCount() == 2) {
  12.                 //调用stop方法停止遍历,stop方法会更新内部的一个标志,置为停止遍历
  13.                 defaultResultContext.stop();
  14.             }
  15.         });
  16.     }
  17. }

运行输出:

  1. 07:05.561 [main] INFO  c.j.chat03.demo4.Demo4Test - ----------
  2. 07:05.816 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==>  Preparing: SELECT * FROM t_user 
  3. 07:05.845 [main] DEBUG c.j.c.d.mapper.UserMapper.getList - ==> Parameters: 
  4. 07:05.864 [main] INFO  c.j.chat03.demo4.Demo4Test - UserModel(id=1, name=路人甲Java, age=30, salary=50000.0, sex=1)
  5. 07:05.867 [main] INFO  c.j.chat03.demo4.Demo4Test - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)

本文的内容希望大家都能掌握。

案例代码获取方式

扫码添加微信备注:mybatis案例,即可获取

MyBatis系列

  1. MyBatis系列第1篇:MyBatis未出世之前我们那些痛苦的经历

  2. MyBatis系列第2篇:入门篇,带你感受一下mybatis独特的魅力!

  3. MyBatis系列第3篇:Mybatis使用详解(1)

  4. MyBatis系列第4篇:Mybatis使用详解(2)

更多好文章

  1. Java高并发系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. 聊聊db和缓存一致性常见的实现方式

  5. 接口幂等性这么重要,它是什么?怎么实现?

感谢大家的阅读,也欢迎您把这篇文章分享给更多的朋友一起阅读!谢谢!

路人甲java

▲长按图片识别二维码关注

路人甲Java:工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活! 来源:https://itsoku.blog.csdn.net/article/details/103485455

posted @ 2022-04-23 03:08  程序员小明1024  阅读(403)  评论(0编辑  收藏  举报