本博文基于:
MyBati3.5.5
JDK 1.8
文章目录
一 ,介绍
在MyBatis中进行接口式编程时,在接口中写上自定义的参数,是如何进行参数处理的?
例如:当个参数的情况。
public Employee getEmployeeByID(Integer id);
<select id="getEmployeeByID" resultType="com.neil.bean.Employee">
SELECT *
FROM tbl_employee
WHERE id = #{id}
</select>
先给出结论
二 ,几种参数类型的用法
2.1 单个参数
- 单个参数: Mybatis 不会做特殊处理 (参数名称可以随便的给出)
#{参数名}
: 取出你绑定的参数值。如上,即使是写成#{fsl}
同样也可以返回(后文源码会分析
) - 多个参数: 多个参数就会被封装成
Map
形式数据。
key: param1… paramN, 或者参数的索引,notice:索引从1开始
value: 传入的参数值
2.2 多个参数
#{}
就是从Map中获取指定的key的值
【Example】:
操作:
public Employee getEmpByIdAndLastName(Integer id, String lastName)
取值:
#{id},#{lastName}
会报错,
Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
解决错误方法,使用命名参数,由于上述方法中取出参数的方法都是通过param-[序号]
的方式来进行指定,所以为了避免麻烦,我们可以通过明确指定封装参数时Map的key;@Param("id")
。
由于我们知道多个参数会封装成Map的形式,key值也有两种,所以,为了方便开发,可以通过注解@Param
来指定Map所对应key的值 。
使用方式:
key: 使用@Param注解指定的值
value: 参数值
#{指定的key}取出对应的参数值
2.3 POJO(Plain Ordinary Java Object)
如果多个参数正好是我们的业务逻辑的数据模型,我们就可以直接传入POJO.
#{属性值} :取出传入的POJO的属性值
2.4 Map
如果是多个参数不是业务模型中的数据,没有对应的POJO,不经常使用,为了方便,我们也可以传入Map
#{key}:取出Map中对应的值
2.5 TO(Transfer Object)
如果多个参数不是业务模型中的数据,但是经常要使用,推荐编写一个TO(Transfer Object)
数据传输对象
page{
int index;
int size;
}
三 ,例子分析
public Employee getEmp(@Param("id") Integer id, String lastName);
取值: id ====> #{id}或者是 #{param1} lastName =====> #{param2}
public Employee getEmp(Integer id, @Param("e")Employee emp);
取值: id ====> #{param1} lastName =====> #{param2.lastName} 或者是 #{e.lastName}
## 特别注意: 如果是Collection(List, Set)
类型或者数组, 也会进行特殊处理, 也就是把传入的list或者数组封装在map中。
key : collection (collection)
,如果是List
还可以使用这个key(list)
, 数组(array)
public Employee getEmpbyId(List<Integer ids>);
取值: 取出第一个id的值: #{list[0]}
四, 源码分析
4.1 前置
4.2 定位
先从执行语句开始,通过代理对象Mapper来执行方法,会进入到代理对象中的invoke
方法执行:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return
// 判断当前是不是Object自带的方法,明显这里不是,执行false中的方法
Object.class.equals(method.getDeclaringClass())
? method.invoke(this, args)
: this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
返回一个MapperMethodInvoker
去调用 invoke方法,这里我们直接看。
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return this.mapperMethod.execute(sqlSession, args);
方法的执行流程全部都在execute
中,
判断是否是哪种方法,这里我们直接找到
param
所有的参数都是已经封装好的参数,所以,所有的逻辑都在convertArgsToSqlCommandParam
方法中。
4.3 分析
所有的逻辑都在 ParamNameResolver
的getNamedParams
方法中:
在开始分析之前,我们要注意names
成员变量获得的逻辑
names参数获得的逻辑,在构造方法中:
public ParamNameResolver(Configuration config, Method method) {
// paramcount获得参数的个数
for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
if (!isSpecialParameter(paramTypes[paramIndex])) {
String name = null;
// 判断param注解个数
Annotation[] var9 = paramAnnotations[paramIndex];
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
Annotation annotation = var9[var11];
if (annotation instanceof Param) {
this.hasParamAnnotation = true;
// 将name赋值为value的值 这里例子为 id lastName
name = ((Param)annotation).value();
break;
}
}
if (name == null) {
// 判断有没有在setting中配置userActualParamName,
//如果配置了useActualParamName=true的话,则取实际参数的名称
if (this.useActualParamName) {
name = this.getActualParamName(method, paramIndex);
}
if (name == null) {
name = String.valueOf(map.size());
}
}
// 最后赋值 map() , 本例子中得到的结果是
//{0 = id , 1 = lastname}
map.put(paramIndex, name);
}
}
this.names = Collections.unmodifiableSortedMap(map);
如果我配置的参数是
(Integer id, @Param(“lastName”) String lastName)
则,names的值为 {0 = 0, 1 = lastName}
后主要分析 getNamedParams
方法:
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
// 如果传入的参数为空则直接返回
if (args != null && paramCount != 0) {
// 如果只有一个元素,并且没有param注解:args[0]:单个参数直接返回,
if (!this.hasParamAnnotation && paramCount == 1) {
Object value = args[(Integer)this.names.firstKey()];
return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);
// 多个元素,或者有parm标注
} else {
Map<String, Object> param = new ParamMap();
int i = 0;
// 保存数据
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
// 遍历names集合{0 = id , 1 = lastName}
Entry<Integer, String> entry = (Entry)var5.next();
// names的value作为key,name集合的key作为取值的参考 args中取出参数
param.put(entry.getValue(), args[(Integer)entry.getKey()]);
//{ id = 1, lastName = zhangsan, 2 = 2}
String genericParamName = "param" + (i + 1);
// 最后无论如何都放入param,是从1开始
// 额外的将每一个参数都保存在map中,使用新的key: param1 , param2
// 所以,除了可以使用#{key}之外,还可以使用#{param1 - N}来进行指定
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}
4.4 总结
ParamNameResover
: 解析参数封装map
的
names: {0 = id, 1 = lastName}
: 构造器确定.
确定流程:
1. 获取每个标了param注解的参数的@Param的值:id, lastName , 赋值给name;如果没有,则默认赋值一个索引。
2. 每次解析一个参数给Map中保存信息:(key:参数索引,value:name的值)
3. name的值:
标注了param的注解:注解的值
没有标注:
全局配置:userActualParamName: name
就是参数名(基于JDK1.8)
name= map.size():相当于元素的索引 {0 = id , 1 = lastName, 2 = 2};
本例中最后返回的param是
notice:他会默认的给你赋值上param-[index]
,index从1开始
【推荐阅读】
MyBatisSQL参数占位符与全局属性useActualParamName详细介绍与原理剖析
mybatis 传递参数的7种方法
深入了解MyBatis参数
【参考】
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#Parameters
https://www.cnblogs.com/mingyue1818/p/3714162.html