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);
1.1.1 只有一个参数,对应的mapper如下:
1 2
无论我们对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]
1.1.2 多个参数
1 4 5 8 9
针对这三个不同的方法,我们分别进行测试
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 @Test16 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 @Test30 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]
从控制台输出可以看到,使用方法1和方法2都可以正确获取数据,而使用方法3则抛出异常,异常的信息为参数id和name没有在参数列表[1, 0, param1, param2]中发现,说明,当前可用的参数为[1, 0, param1, param2],这也应证了方法1和方法2中可以正确获取参数。
1.1.3 使用map进行传参
1
构造测试用例
1 @Test 2 public void testFindByIdAndNameByMap() { 3 UserService userService = (UserService) act.getBean("userService"); 4 HashMapmap = new HashMap (); 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]
同样可以正确获取数据
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);
1.2.1 只有一个参数,对应的mapper如下:
mapper配置如下:
1 2 5 6 9 10
分别对这三个方法进行测试用例,结果如下:
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 @Test12 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 @Test26 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]
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 }
其主要功能就是针对方法的类型,进行具体的数据操作,因为我们主要是分析参数的传递,所以我们关键看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 Mapparam = new ParamMap
进入这个方法可以具体看到,针对不同的参数个数对其进行处理,在这个方法刚刚进入时,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 }
从最后一句getParams方法中可以看出,这个方法是对参数进行获取,进入这个方法:
1 private SortedMapgetParams(Method method, boolean hasNamedParameters) { 2 final SortedMap params = new TreeMap (); 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 }
可以看到,中间有个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;
mapper.xml的配置文件为
1 2 5 6 7 8 12 13 14 17 18 1920 21 22 23 24 25
我们通过测试用例进行测试:
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 @Test16 public void testFindById3() {17 UserService userService = (UserService) act.getBean("userService");18 User u = userService.findById3(1);19 System.out.println(u);20 }
我们发现:
1、testFindById1方法执行查询后返回一个null。
2、testFindById2方法和testFindById3方法执行查询均可获取正确的数据。
所以,当实体类中的属性名和表中的字段名不一致时,可以通过以下方式进行解决:
解决办法一: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致,这样就可以表的字段名和实体类的属性名一一对应上了,这种方式是通过在sql语句中定义别名来解决字段名和属性名的映射关系的。
解决办法二: 通过<resultMap>来映射字段名和实体类属性名的一一对应关系。这种方式是使用MyBatis提供的解决方式来解决字段名和属性名的映射关系的。
本文地址:http://www.cnblogs.com/gzy-blog/p/6079512.html