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

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

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

 public User findById(int id);

 public User findByIdAndName1(int id, String name);

 public User findByIdAndName2(int id, String name);

 public User findByIdAndName3(nt id, String name);

1.1.1 只有一个参数,对应的mapper如下:

 <!-- 根据主键查找 -->
<select id="findById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>

无论我们对sql语句中的#{id}进行任何命名,测试用例都可以正确获取值

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

1.1.2 多个参数

 <select id="findByIdAndName1"  resultType="user">
select * from user where id = #{0} and name =#{1}
</select> <select id="findByIdAndName2" resultType="user">
select * from user where id = #{param1} and name =#{param2}
</select> <select id="findByIdAndName3" resultType="user">
select * from user where id = #{id} and name =#{name}
</select>

针对这三个不同的方法,我们分别进行测试

 @Test
public void testFindByIdAndName1() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findByIdAndName1(1,"hello");
System.out.println(u);
} <!-------- 控制台输出--------->
2016-11-19 09:25:41 - MLog clients using log4j logging.
2016-11-19 09:25:41 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}"
2016-11-19 09:25:42 - Initializing c3p0 pool...
User [id=1, name=hello, age=23] @Test
public void testFindByIdAndName2() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findByIdAndName2(1,"hello");
System.out.println(u);
} <!-------- 控制台输出--------->
2016-11-19 09:25:41 - MLog clients using log4j logging.
2016-11-19 09:25:41 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}"
2016-11-19 09:25:42 - Initializing c3p0 pool...
User [id=1, name=hello, age=23] @Test
public void testFindByIdAndName3() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findByIdAndName3(1,"hello");
System.out.println(u);
} <!-------- 控制台输出--------->
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进行传参

 <select id="findByIdAndNameByMap"  resultType="user">
select * from user where id = #{id} and name =#{name}
</select>

构造测试用例

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

同样可以正确获取数据

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

 public User findById(@Param(value="id")int id);

 public User findByIdAndName1(@Param(value="id")int id, @Param(value="name")String name);

 public User findByIdAndName2(@Param(value="id")int id, @Param(value="name")String name);

 public User findByIdAndName3(@Param(value="id")int id, @Param(value="name")String name);

1.2.1 只有一个参数,对应的mapper如下:

mapper配置如下:

 <!-- 根据主键查找 -->
<select id="findById1" parameterType="int" resultType="user">
select * from user where id = #{0}
</select> <select id="findById2" parameterType="int" resultType="user">
select * from user where id = #{param1}
</select> <select id="findById3" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>

分别对这三个方法进行测试用例,结果如下:

 @Test
public void testFindById1() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findById1(1);
System.out.println(u);
} <!-----------控制台输出----------------->
Caused by: org.apache.ibatis.binding.BindingException: Parameter '0' not found. Available parameters are [id, param1] @Test
public void testFindById2() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findById2(1);
System.out.println(u);
} <!-----------控制台输出----------------->
2016-11-19 10:08:34 - MLog clients using log4j logging.
2016-11-19 10:08:34 - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
2016-11-19 10:08:35 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public void com.ssm.controller.UserController.test()
2016-11-19 10:08:35 - Initializing c3p0 pool...
User [id=1, name=hello, age=23] @Test
public void testFindById3() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findById3(1);
System.out.println(u);
} <!-----------控制台输出----------------->
2016-11-19 10:08:34 - MLog clients using log4j logging.
2016-11-19 10:08:34 - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
2016-11-19 10:08:35 - Mapped "{[/test],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public void com.ssm.controller.UserController.test()
2016-11-19 10:08:35 - Initializing c3p0 pool...
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方法:

  public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

其主要功能就是针对方法的类型,进行具体的数据操作,因为我们主要是分析参数的传递,所以我们关键看convertArgsToSqlCommandParam这个方法,从方法名可以看出,这个方法的功能是将参数转换为sql命令中的参数。

 public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}

进入这个方法可以具体看到,针对不同的参数个数对其进行处理,在这个方法刚刚进入时,final int paramCount = params.size();不仅仅要获取参数的个数,其实在params初始化时,已经对配置@Param注解的参数进行处理,这个初始化过程中本类的构造方法中进行:

 public MethodSignature(Configuration configuration, Method method) throws BindingException {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}

从最后一句getParams方法中可以看出,这个方法是对参数进行获取,进入这个方法:

 private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
String paramName = String.valueOf(params.size());
if (hasNamedParameters) {
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
return params;
}

可以看到,中间有个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表的字段进行一些修改如下:

 CREATE TABLE `user` (
`t_id` int(11) NOT NULL auto_increment,
`t_name` varchar(255) default NULL,
`t_age` int(11) default NULL,
PRIMARY KEY (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

mapper.xml的配置文件为

 <!-- 根据id查询得到一个user对象,查询不到结果, 这主要是因为实体类的属性名和数据库的字段名对应不上的原因,因此无法查询出对应的记录 -->
<select id="findById1" parameterType="int" resultType="user">
select * from user where t_id = #{0}
</select> <!-- 根据id查询得到一个user对象,使用这个查询可以得到正确的数据, 这是因为我们将查询的字段名都起一个和实体类属性名相同的别名,这样实体类的属性名和查询结果中的字段名就可以一一对应上 -->
<select id="findById2" parameterType="int" resultType="user">
select t_id id ,t_name name, t_age age
from user where t_id = #{param1}
</select> <!-- 根据id查询得到一个order对象,使用这个查询可以得到正确的数据,这是因为我们通过<resultMap>映射实体类属性名和表的字段名一一对应关系 -->
<select id="findById3" parameterType="int" resultMap="userResultMap">
select * from user where t_id = #{id}
</select> <!--通过<resultMap>映射实体类属性名和表的字段名对应关系 -->
<resultMap type="com.ssm.pojo.User" id="userResultMap">
<!-- 用id属性来映射主键字段 -->
<id property="id" column="t_id" />
<!-- 用result属性来映射非主键字段 -->
<result property="name" column="t_name" />
<result property="age" column="t_age" />
</resultMap>

我们通过测试用例进行测试:

 @Test
public void testFindById1() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findById1(1);
System.out.println(u);
} @Test
public void testFindById2() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findById2(1);
System.out.println(u);
} @Test
public void testFindById3() {
UserService userService = (UserService) act.getBean("userService");
User u = userService.findById3(1);
System.out.println(u);
}

我们发现:

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

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

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

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

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

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

MyBatis参数绑定规则及原理分析的更多相关文章

  1. .net core Web API参数绑定规则

    参数推理绑定 先从一个问题说起,微信小程序按照WebAPI定义的参数传递,Get请求服务器端可以正常接收到参数,但是Post请求取不到. Web API代码(.netcore 3.1)如下: [Htt ...

  2. apidoc快速生成在线文档,apidoc生成静态文件的生成规则以及原理分析

    在老大的指引下,需要将系统的json文件格式转换成apidoc的json格式,也就是json格式的重组,但是这个apidoc的生成格式是不固定的,因为apidoc有自己一套的生成规则,我需要研究一下是 ...

  3. Mybatis Plus启动注入 SQL 原理分析

    1) 问题: xxxMapper 继承了 BaseMapper<T>, BaseMapper 中提供了通用的 CRUD 方法, 方法来源于 BaseMapper, 有方法就必须有 SQL, ...

  4. webapi frombody fromuri的参数绑定规则

    在WebAPI中,请求主体(HttpContent)只能被读取一次,不被缓存,只能向前读取的流. 举例子说明: 1. 请求地址:/?id=123&name=bob 服务端方法: void Ac ...

  5. SpringMVC介绍及参数绑定

    本节内容: SpringMVC介绍 入门程序 SpringMVC架构 SpringMVC整合MyBatis 参数绑定 SpringMVC和Struts2的区别 一.SpringMVC介绍 1. 什么是 ...

  6. ReentrantLock原理分析

    一 UML类图 通过类图ReentrantLock是同步锁,同一时间只能有一个线程获取到锁,其他获取该锁的线程会被阻塞而被放入AQS阻塞队列中.ReentrantLock类继承Lock接口:内部抽象类 ...

  7. MyBatis的深入原理分析之1-架构设计以及实例分析

    MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...

  8. vue2.0 双向绑定原理分析及简单实现

    Vue用了有一段时间了,每当有人问到Vue双向绑定是怎么回事的时候,总是不能给大家解释的很清楚,正好最近有时间把它梳理一下,让自己理解的更清楚,下次有人问我的时候,可以侃侃而谈. 一.首先介绍Obje ...

  9. MyBatis框架及原理分析

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架,其主要就完成2件事情: 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转换 MyBatis的主要设计目的就 ...

随机推荐

  1. 转: Delphi的OverRide、OverLoad和Virtual方法

    http://blog.csdn.net/ckli/article/details/2201418 override 重写 也叫覆盖 .方法的重写Overriding和重载Overloading是Ja ...

  2. C# Current thread must be set to single thread apartment (STA) mode before OLE calls can be made

    将箭头指向部分替换为编译器报错的内容即可. 参考文章:https://www.experts-exchange.com/questions/28238490/C-help-needed-Current ...

  3. nandaom

    this python</div><div><br></div><div>hahah</div><div> 来自为知 ...

  4. JAXP简介

    JAXP(Java API for XML Processing,意为XML处理的Java API) JAXP是SUN公司推出的,集成在javase中的用来解析和操作XML的应用程序接口,解析XML文 ...

  5. 无法加载 DLL“SQLite.Interop.dll”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)

    SQLite部署-无法加载 DLL“SQLite.Interop.dll”: 找不到指定的模块 近期刚使用SQLite,主要引用的是System.Data.SQLite.dll这个dll,在部署到测试 ...

  6. Linux Shell 截取字符串

    Linux Shell 截取字符串 shell中截取字符串的方法很多 ${var#*/} ${var##*/} ${var%/*} ${var%%/*} ${var:start:len} ${var: ...

  7. CruiseControl.Net <buildpublisher>部署到远程机器报错的解决办法

    CruiseControl.Net ,使用<buildpublisher>将编译后的程序部署到远程机器时,使用以下配置 <buildpublisher> <sourceD ...

  8. PHPExcel按单元格读取数据

    import('ORG.Util.PHPExcel.PHPExcel'); $objReader = new PHPExcel_Reader_Excel2007(); //use excel2007 ...

  9. Openssl生成证书三板斧

    证书创建三步曲: 一.密钥文件 二.请求文 三.根证书签名 最后看需要是否合并证书文件 1. 创立根证书密钥文件(自己做CA)root.key: [kk@test ~]$ openssl genrsa ...

  10. STL之priority_queue

    下面以 long long 型队列介绍: Q.empty() // 判断队列是否为空 返回ture表示空 返回false表示空 bool Q.top() // 返回顶端元素的值 元素还在队列里 lon ...