zoukankan      html  css  js  c++  java
  • MyBatis参数绑定规则及原理分析

    MyBatis参数的传递有几种不同的方法,本文通过测试用例出发,对其中的方式进行总结和说明,并对其部分源码进行分析。

    一、测试用例(环境参考之前博客SSM接口编程一文 http://www.cnblogs.com/gzy-blog/p/6052185.html)

    1.1 没有注解,即dao层的代码如下: 

    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}进行任何命名,测试用例都可以正确获取值

     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中可以正确获取参数。

    1.1.3 使用map进行传参 

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

     构造测试用例

     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正确执行。

    二、参数规则结论

    1、没有@Param注解参数

      1.1参数只有一个 ==>  #{任意字符}

      1.2多个参数 ==> #{参数位置[0..n-1]}或者#{param[1..n]}

      1.3参数为自定义类型 ==> #{参数位置[0..n-1].对象属性}或者#{param[1..n].对象属性}

    2、有@Param注解参数

      2.1无论参数个数是一个还是多个 ==>  #{注解别名} 或者 #{param[1..n]} 

      2.2参数为自定义类型 ==> #{注解别名.属性}或者#{param[1..n].属性}

    3、Map封装多参数  

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

    三、源码分析

    在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语句中,说明有三种情况:

      1、入参为null或没有时,参数转换为null;

      2、没有使用@Param 注解并且只有一个参数时,返回这一个参数

      3、使用了@Param 注解或有多个参数时,将参数转换为Map1类型,并且还根据参数顺序存储了key为param1,param2的参数。

    这也证明了我们可以通过map来进行参数传递,在传入map时,实际走的分支是第2个分支,参数数组中只有一个对象,这个对象是map类型的,把数组中的第一个元素返回,这和多个参数走第三个分分支效果一样,在第三个分支中,可以看到是返回一个ParamMap,这个ParamMap实际也是继承至HashMap。 public static class ParamMap<V> extends HashMap<String, V>。所以两者实现的效果是一样的。

    四、问题与解决方法

    通过上述分析,我们把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

    我们发现:

    1、testFindById1方法执行查询后返回一个null。

    2、testFindById2方法和testFindById3方法执行查询均可获取正确的数据。

    所以,当实体类中的属性名和表中的字段名不一致时,可以通过以下方式进行解决:

    解决办法一: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致,这样就可以表的字段名和实体类的属性名一一对应上了,这种方式是通过在sql语句中定义别名来解决字段名和属性名的映射关系的。

    解决办法二: 通过<resultMap>来映射字段名和实体类属性名的一一对应关系。这种方式是使用MyBatis提供的解决方式来解决字段名和属性名的映射关系的。

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

  • 相关阅读:
    Gitlab 与 Git Windows 客户端一起使用的入门流程
    怎样把SEL放进NSArray里
    PerformSelector may cause a leak because its selector is unknown 解决方法
    drawRect
    记录常规越狱的判断方法
    网页 js
    UICollectionView 基础
    FMDB的简单使用
    图层的一些基本动画效果
    NSPredicate简单介绍
  • 原文地址:https://www.cnblogs.com/gzy-blog/p/6079512.html
Copyright © 2011-2022 走看看