MyBatis參數綁定規則及原理分析

MyBatis參數的傳遞有幾種不一樣的方法,本文經過測試用例出發,對其中的方式進行總結和說明,並對其部分源碼進行分析。html

1、測試用例(環境參考以前博客SSM接口編程一文 http://www.cnblogs.com/gzy-blog/p/6052185.html)java

1.1 沒有註解,即dao層的代碼以下: sql

1 public User findById(int id);
2 
3 public User findByIdAndName1(int id, String name);
4     
5 public User findByIdAndName2(int id, String name);
6     
7 public User findByIdAndName3(nt id, String name);
View Code

 1.1.1 只有一個參數,對應的mapper以下:數據庫

1 <!-- 根據主鍵查找 -->
2 <select id="findById" parameterType="int" resultType="user">
3     select * from user where id = #{id} 
4 </select>
View Code

 不管咱們對sql語句中的#{id}進行任何命名,測試用例均可以正確獲取值apache

 1 @Test
 2 public void testFindById() {
 3     UserService userService = (UserService) act.getBean("userService");
 4     User u = userService.findById(1);
 5     System.out.println(u);
 6 }
 7 
 8 2016-11-19 09:10:30 - Initializing c3p0-0.9.1.2 
 9 2016-11-19 09:10:31 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}"
10 2016-11-19 09:10:31 - Initializing c3p0 pool... 
11 User [id=1, name=hello, age=23]
View Code

1.1.2 多個參數編程

 1 <select id="findByIdAndName1"  resultType="user">
 2     select * from user where id = #{0} and name =#{1}
 3 </select>
 4     
 5 <select id="findByIdAndName2"  resultType="user">
 6     select * from user where id = #{param1} and name =#{param2}
 7 </select>
 8     
 9 <select id="findByIdAndName3"  resultType="user">
10     select * from user where id = #{id} and name =#{name}
11 </select>
View Code

針對這三個不一樣的方法,咱們分別進行測試 數組

 1 @Test
 2 public void testFindByIdAndName1() {
 3     UserService userService = (UserService) act.getBean("userService");
 4     User u = userService.findByIdAndName1(1,"hello");
 5     System.out.println(u);
 6 }
 7 
 8 <!--------  控制檯輸出--------->
 9 2016-11-19 09:25:41 - MLog clients using log4j logging.
10 2016-11-19 09:25:41 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}"
11 2016-11-19 09:25:42 - Initializing c3p0 pool... 
12 User [id=1, name=hello, age=23]
13 
14     
15 @Test
16 public void testFindByIdAndName2() {
17     UserService userService = (UserService) act.getBean("userService");
18     User u = userService.findByIdAndName2(1,"hello");
19     System.out.println(u);
20 }
21 
22 <!--------  控制檯輸出--------->
23 2016-11-19 09:25:41 - MLog clients using log4j logging.
24 2016-11-19 09:25:41 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}"
25 2016-11-19 09:25:42 - Initializing c3p0 pool... 
26 User [id=1, name=hello, age=23]
27     
28 
29 @Test
30 public void testFindByIdAndName3() {
31     UserService userService = (UserService) act.getBean("userService");
32     User u = userService.findByIdAndName3(1,"hello");
33     System.out.println(u);
34 }
35 
36 <!--------  控制檯輸出--------->
37 Caused by: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [1, 0, param1, param2]
View Code

從控制檯輸出能夠看到,使用方法1和方法2均可以正確獲取數據,而使用方法3則拋出異常,異常的信息爲參數id和name沒有在參數列表[1, 0, param1, param2]中發現,說明,當前可用的參數爲[1, 0, param1, param2],這也應證了方法1和方法2中能夠正確獲取參數。mybatis

1.1.3 使用map進行傳參 app

1 <select id="findByIdAndNameByMap"  resultType="user">
2     select * from user where id = #{id} and name =#{name}
3 </select>
View Code

 構造測試用例ide

 1 @Test
 2 public void testFindByIdAndNameByMap() {
 3     UserService userService = (UserService) act.getBean("userService");
 4     HashMap<String,String> map = new HashMap<String,String>();
 5     map.put("id", "1");
 6     map.put("name", "hello");
 7     User u = userService.findByIdAndNameByMap(map);
 8     System.out.println(u);
 9 }
10 
11 <!---------------------控制檯輸出---------------------------->
12 2016-11-19 09:55:49 - MLog clients using log4j logging.
13 2016-11-19 09:55:49 - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
14 2016-11-19 09:55:49 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public void com.ssm.controller.UserController.test()
15 2016-11-19 09:55:50 - Initializing c3p0 pool... 
16 User [id=1, name=hello, age=23]
View Code

一樣能夠正確獲取數據

1.2 有註解,即dao層的代碼以下: 

1 public User findById(@Param(value="id")int id);
2 
3 public User findByIdAndName1(@Param(value="id")int id, @Param(value="name")String name);
4     
5 public User findByIdAndName2(@Param(value="id")int id, @Param(value="name")String name);
6     
7 public User findByIdAndName3(@Param(value="id")int id, @Param(value="name")String name);
View Code

 1.2.1 只有一個參數,對應的mapper以下: 

mapper配置以下:

 1 <!-- 根據主鍵查找 -->
 2 <select id="findById1" parameterType="int" resultType="user">
 3     select * from user where id = #{0} 
 4 </select>
 5 
 6 <select id="findById2" parameterType="int" resultType="user">
 7     select * from user where id = #{param1} 
 8 </select>
 9 
10 <select id="findById3" parameterType="int" resultType="user">
11     select * from user where id = #{id} 
12 </select>
View Code

分別對這三個方法進行測試用例,結果以下:

 1 @Test
 2 public void testFindById1() {
 3     UserService userService = (UserService) act.getBean("userService");
 4     User u = userService.findById1(1);
 5     System.out.println(u);
 6 }
 7 
 8 <!-----------控制檯輸出----------------->
 9 Caused by: org.apache.ibatis.binding.BindingException: Parameter '0' not found. Available parameters are [id, param1]
10     
11 @Test
12 public void testFindById2() {
13     UserService userService = (UserService) act.getBean("userService");
14     User u = userService.findById2(1);
15     System.out.println(u);
16 }
17 
18 <!-----------控制檯輸出----------------->
19 2016-11-19 10:08:34 - MLog clients using log4j logging.
20 2016-11-19 10:08:34 - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
21 2016-11-19 10:08:35 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public void com.ssm.controller.UserController.test()
22 2016-11-19 10:08:35 - Initializing c3p0 pool... 
23 User [id=1, name=hello, age=23]
24     
25 @Test
26 public void testFindById3() {
27     UserService userService = (UserService) act.getBean("userService");
28     User u = userService.findById3(1);
29     System.out.println(u);
30 }
31 
32 <!-----------控制檯輸出----------------->
33 2016-11-19 10:08:34 - MLog clients using log4j logging.
34 2016-11-19 10:08:34 - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
35 2016-11-19 10:08:35 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public void com.ssm.controller.UserController.test()
36 2016-11-19 10:08:35 - Initializing c3p0 pool... 
37 User [id=1, name=hello, age=23]
View Code

1.1.2 多個參數

測試結果與1.2.1中的結果相同,方法一拋出「0」不在參數列表中的異常,方法2和方法3正確執行。

2、參數規則結論

一、沒有@Param註解參數

  1.1參數只有一個 ==>  #{任意字符}

  1.2多個參數 ==> #{參數位置[0..n-1]}或者#{param[1..n]}

  1.3參數爲自定義類型 ==> #{參數位置[0..n-1].對象屬性}或者#{param[1..n].對象屬性}

二、有@Param註解參數

  2.1不管參數個數是一個仍是多個 ==>  #{註解別名} 或者 #{param[1..n]} 

  2.2參數爲自定義類型 ==> #{註解別名.屬性}或者#{param[1..n].屬性}

三、Map封裝多參數  

  其中hashmap是mybatis本身配置好的直接使用就行。map中key的名字是那個就在#{}使用那個

3、源碼分析

在package org.apache.ibatis.binding.MapperMethid.java中有execute方法: 

 1  public Object execute(SqlSession sqlSession, Object[] args) {
 2     Object result;
 3     if (SqlCommandType.INSERT == command.getType()) {
 4       Object param = method.convertArgsToSqlCommandParam(args);
 5       result = rowCountResult(sqlSession.insert(command.getName(), param));
 6     } else if (SqlCommandType.UPDATE == command.getType()) {
 7       Object param = method.convertArgsToSqlCommandParam(args);
 8       result = rowCountResult(sqlSession.update(command.getName(), param));
 9     } else if (SqlCommandType.DELETE == command.getType()) {
10       Object param = method.convertArgsToSqlCommandParam(args);
11       result = rowCountResult(sqlSession.delete(command.getName(), param));
12     } else if (SqlCommandType.SELECT == command.getType()) {
13       if (method.returnsVoid() && method.hasResultHandler()) {
14         executeWithResultHandler(sqlSession, args);
15         result = null;
16       } else if (method.returnsMany()) {
17         result = executeForMany(sqlSession, args);
18       } else if (method.returnsMap()) {
19         result = executeForMap(sqlSession, args);
20       } else {
21         Object param = method.convertArgsToSqlCommandParam(args);
22         result = sqlSession.selectOne(command.getName(), param);
23       }
24     } else {
25       throw new BindingException("Unknown execution method for: " + command.getName());
26     }
27     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
28       throw new BindingException("Mapper method '" + command.getName() 
29           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
30     }
31     return result;
32   }
View Code

 其主要功能就是針對方法的類型,進行具體的數據操做,由於咱們主要是分析參數的傳遞,因此咱們關鍵看convertArgsToSqlCommandParam這個方法,從方法名能夠看出,這個方法的功能是將參數轉換爲sql命令中的參數。

 1 public Object convertArgsToSqlCommandParam(Object[] args) {
 2       final int paramCount = params.size();
 3       if (args == null || paramCount == 0) {
 4         return null;
 5       } else if (!hasNamedParameters && paramCount == 1) {
 6         return args[params.keySet().iterator().next()];
 7       } else {
 8         final Map<String, Object> param = new ParamMap<Object>();
 9         int i = 0;
10         for (Map.Entry<Integer, String> entry : params.entrySet()) {
11           param.put(entry.getValue(), args[entry.getKey()]);
12           // issue #71, add param names as param1, param2...but ensure backward compatibility
13           final String genericParamName = "param" + String.valueOf(i + 1);
14           if (!param.containsKey(genericParamName)) {
15             param.put(genericParamName, args[entry.getKey()]);
16           }
17           i++;
18         }
19         return param;
20       }
21     }
View Code

進入這個方法能夠具體看到,針對不一樣的參數個數對其進行處理,在這個方法剛剛進入時,final int paramCount = params.size();不只僅要獲取參數的個數,其實在params初始化時,已經對配置@Param註解的參數進行處理,這個初始化過程當中本類的構造方法中進行:

 1 public MethodSignature(Configuration configuration, Method method) throws BindingException {
 2       this.returnType = method.getReturnType();
 3       this.returnsVoid = void.class.equals(this.returnType);
 4       this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
 5       this.mapKey = getMapKey(method);
 6       this.returnsMap = (this.mapKey != null);
 7       this.hasNamedParameters = hasNamedParams(method);
 8       this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
 9       this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
10       this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
11     }
View Code

從最後一句getParams方法中能夠看出,這個方法是對參數進行獲取,進入這個方法:

 1 private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
 2       final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
 3       final Class<?>[] argTypes = method.getParameterTypes();
 4       for (int i = 0; i < argTypes.length; i++) {
 5         if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
 6           String paramName = String.valueOf(params.size());
 7           if (hasNamedParameters) {
 8             paramName = getParamNameFromAnnotation(method, i, paramName);
 9           }
10           params.put(i, paramName);
11         }
12       }
13       return params;
14     }
View Code

能夠看到,中間有個getParamNameFromAnnotation方法,這個方法就是利用@Param註解獲取對應的參數名稱,能夠到帶有註解@Param,params獲取的值爲{0=id, 1=name},而不帶註解params獲取的值爲{0=0, 1=1},繼續分析convertArgsToSqlCommandParam方法。從if語句中,說明有三種狀況:

  一、入參爲null或沒有時,參數轉換爲null;

  二、沒有使用@Param 註解而且只有一個參數時,返回這一個參數

  三、使用了@Param 註解或有多個參數時,將參數轉換爲Map1類型,而且還根據參數順序存儲了key爲param1,param2的參數。

這也證實了咱們能夠經過map來進行參數傳遞,在傳入map時,實際走的分支是第2個分支,參數數組中只有一個對象,這個對象是map類型的,把數組中的第一個元素返回,這和多個參數走第三個分分支效果同樣,在第三個分支中,能夠看到是返回一個ParamMap,這個ParamMap實際也是繼承至HashMap。 public static class ParamMap<V> extends HashMap<String, V>。因此二者實現的效果是同樣的。

4、問題與解決方法

經過上述分析,咱們把Mybatis的參數傳遞的規則和原理進行了分析,那麼有個問題,咱們以前使用的實體類的字段屬性和數據庫中中的字段是一直的,那麼二者若是不一致該如何處理呢?例如,咱們把咱們數據庫user表的字段進行一些修改以下: 

1 CREATE TABLE `user` (
2   `t_id` int(11) NOT NULL auto_increment,
3   `t_name` varchar(255) default NULL,
4   `t_age` int(11) default NULL,
5   PRIMARY KEY  (`t_id`)
6 ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
View Code

 mapper.xml的配置文件爲

 1 <!-- 根據id查詢獲得一個user對象,查詢不到結果, 這主要是由於實體類的屬性名和數據庫的字段名對應不上的緣由,所以沒法查詢出對應的記錄 -->
 2 <select id="findById1" parameterType="int" resultType="user">
 3     select * from user where t_id = #{0}
 4 </select>
 5 
 6 
 7 <!-- 根據id查詢獲得一個user對象,使用這個查詢能夠獲得正確的數據, 這是由於咱們將查詢的字段名都起一個和實體類屬性名相同的別名,這樣實體類的屬性名和查詢結果中的字段名就能夠一一對應上 -->
 8 <select id="findById2" parameterType="int" resultType="user">
 9     select t_id id ,t_name name, t_age age
10         from user where t_id = #{param1}
11 </select>
12 
13 <!-- 根據id查詢獲得一個order對象,使用這個查詢能夠獲得正確的數據,這是由於咱們經過<resultMap>映射實體類屬性名和表的字段名一一對應關係 -->
14 <select id="findById3" parameterType="int" resultMap="userResultMap">
15     select * from user where t_id = #{id}
16 </select>
17 
18 <!--經過<resultMap>映射實體類屬性名和表的字段名對應關係 -->
19 <resultMap type="com.ssm.pojo.User" id="userResultMap">
20     <!-- 用id屬性來映射主鍵字段 -->
21     <id property="id" column="t_id" />
22     <!-- 用result屬性來映射非主鍵字段 -->
23     <result property="name" column="t_name" />
24     <result property="age" column="t_age" />
25 </resultMap>
View Code

咱們經過測試用例進行測試:

 1 @Test
 2 public void testFindById1() {
 3     UserService userService = (UserService) act.getBean("userService");
 4     User u = userService.findById1(1);
 5     System.out.println(u);
 6 }
 7     
 8 @Test
 9 public void testFindById2() {
10     UserService userService = (UserService) act.getBean("userService");
11     User u = userService.findById2(1);
12     System.out.println(u);
13 }
14     
15 @Test
16 public void testFindById3() {
17     UserService userService = (UserService) act.getBean("userService");
18     User u = userService.findById3(1);
19     System.out.println(u);
20 }
View Code

咱們發現:

一、testFindById1方法執行查詢後返回一個null。

二、testFindById2方法和testFindById3方法執行查詢都可獲取正確的數據。

因此,當實體類中的屬性名和表中的字段名不一致時,能夠經過如下方式進行解決:

解決辦法一: 經過在查詢的sql語句中定義字段名的別名,讓字段名的別名和實體類的屬性名一致,這樣就能夠表的字段名和實體類的屬性名一一對應上了,這種方式是經過在sql語句中定義別名來解決字段名和屬性名的映射關係的。

解決辦法二: 經過<resultMap>來映射字段名和實體類屬性名的一一對應關係。這種方式是使用MyBatis提供的解決方式來解決字段名和屬性名的映射關係的。

本文地址:http://www.cnblogs.com/gzy-blog/p/6079512.html

相關文章
相關標籤/搜索