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)

使用注意

  1. 使用參數名稱的方式對編譯環境有很強的依賴性,如果編譯中加上了`-parameters`參數,參數實際名稱可以直接使用,如果沒有加,參數名稱就變成`arg下標`的格式了,這種很容易出錯
  2. 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(collectionlist)都可以引用到這個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的方式,遍歷ResultSetnext方法,一條條處理,而不用將其存到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、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!