mapper的代理对象生成位于org.apache.ibatis.binding.MapperProxyFactory的newInstance方法,使用jdk的动态代理,代理的InvocationHandler为org.apache.ibatis.binding.MapperProxy。

调用代理对象的方法会走MapperProxy的invoke方法。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {//执行继承object类的方法
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {//执行default方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//封装mapperMethod,执行mapper的接口方法
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
} private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}

追踪到MapperMethod的execute方法,可以看到它是通过class+method的全路径得到对应的MappedStatement,然后通过MappedStatement的SqlCommandType和返回类型调用对用的SqlSession方法。

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {//判断MappedStatement的SqlCommandType
case INSERT: {//如果是insert调用sqlSession.insert
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));//rowCountResult根据返回值返回int、long、boolean
break;
}
case UPDATE: {//如果是update调用sqlSession.update
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {//如果是delete调用sqlSession.delete
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT://如果是select判断返回值和参数
if (method.returnsVoid() && method.hasResultHandler()) {//如果返回值是void,且参数包含ResultHandler类型的参数,调用携带ResultHandler的SqlSession.select方法
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {//如果返回值为数组或者Collection,调用SqlSession的selectList方法
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//如果返回值为Map且方法上由@MapKey注解,调用SqlSession的selectMap方法
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {//如果返回值为Cursor,调用SqlSession的selectCursor方法
result = executeForCursor(sqlSession, args);
} else {//调用SqlSession的selectOne方法
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}

接下来我们看下面的代码:

	@Update("update user set username = #{username} and password = #{password} where id = #{id}")
boolean updateById1(User user); @Update("update user set username = #{username} and password = #{password} where id = #{id}")
boolean updateById2(@Param("eo")User user); @Update("update user set username = #{username} and password = #{password} where id = #{id}")
boolean updateById3(Long id, User user); @Update("update user set username = #{username} and password = #{password} where id = #{id}")
boolean updateById4(@Param("id")Long id, @Param("eo")User user);

运行结果会发现只有第一个方法可以运行成功,而后面会报错,如果改成下面会运行成功

	@Update("update user set username = #{username} and password = #{password} where id = #{id}")
boolean updateById1(User user); @Update("update user set username = #{eo.username} and password = #{eo.password} where id = #{eo.id}")
boolean updateById2(@Param("eo")User user); @Update("update user set username = #{param2.username} and password = #{param2.password} where id = #{param1}")
boolean updateById3(Long id, User user); @Update("update user set username = #{eo.username} and password = #{eo.password} where id = #{id}")
boolean updateById4(@Param("id")Long id, @Param("eo")User user);

导致这样结果的代码就在于Object param = method.convertArgsToSqlCommandParam(args);将参数转换为SqlSession的参数中。

我们往下追踪可以看到ParamNameResolver的getNamedParams:

public class ParamNameResolver {

  private static final String GENERIC_NAME_PREFIX = "param";

    //names结构为key为参数的index,value为参数名
//例如:boolean updateById4(@Param("id")Long id, @Param("eo")User user); 会转换为{{0,"id"}, {1, "eo"}}
//boolean updateById3(Long id, User user); value在jdk8以上且编译加上-parameters参数名为id,user,没有-parameters为无意义的arg0,arg1,而在jdk8以下由于获取不到参数名,所以取值为index
private final SortedMap<Integer, String> names; private boolean hasParamAnnotation; public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
//跳过rowbounds和resultHandler类型的参数
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
//如果参数有@Param注解,使用param的value
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// 如果没有使用param获取参数名
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
//jdk8以下使用index
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
} public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {//没有使用注解和参数只有一个,直接返回
return args[names.firstKey()];
} else {
//将参数封装为paramMap,同时存储两份数据,一份为name-value,一份为param[index]-value
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}

所以上面的接口参数会转换为

    boolean updateById1(User user);//->user

    boolean updateById2(@Param("eo")User user);//->paramMap{{"eo", user},{"param1", user}}

    boolean updateById3(Long id, User user);//jdk8以上没有开启parameters ->paramMap {{"arg0", id}, {"param1",id}, {"arg1", user}, {"param2",user}}

    boolean updateById4(@Param("id")Long id, @Param("eo")User user);//->paramMap{{"id", id}, {"param1", id}, {"eo", user}, {"param2", user}}

Mybatis——Mapper代理的更多相关文章

  1. mybatis入门基础(二)----原始dao的开发和mapper代理开发

    承接上一篇 mybatis入门基础(一) 看过上一篇的朋友,肯定可以看出,里面的MybatisService中存在大量的重复代码,看起来不是很清楚,但第一次那样写,是为了解mybatis的执行步骤,先 ...

  2. Spring+SpringMVC+Mybatis大整合(SpringMVC采用REST风格、mybatis采用Mapper代理)

    整体目录结构: 其中包下全部是采用mybatis自动生成工具生成. mybatis自动生成文件 <?xml version="1.0" encoding="UTF- ...

  3. mybatis——使用mapper代理开发方式

    ---------------------------------------------------------------generatorConfig.xml------------------ ...

  4. MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析

    我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...

  5. mybatis入门-mapper代理原理

    原始dao层开发 在我们用mybatis开发了第一个小程序后,相信大家对于dao层的开发其实已经有了一个大概的思路了.其他的配置不用变,将原来的test方法,该为dao的方法,将原来的返回值,直接在d ...

  6. mybatis系列笔记(2)---mapper代理方法

    mapper代理方法 在我们在写MVC设计的时候,都会写dao层和daoimp实现层,但假如我们使用mapper代理的方法,我们就可以不用先daoimp实现类 当然这得需要遵守一些相应的规则: (1) ...

  7. Spring+SpringMVC+MyBatis深入学习及搭建(二)——MyBatis原始Dao开发和mapper代理开发

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6869133.html 前面有写到Spring+SpringMVC+MyBatis深入学习及搭建(一)——My ...

  8. 用mybatis实现dao的编写或者实现mapper代理

    一.mybatis和hibernate的区别和应用场景hibernate:是一个标准的ORM框架(对象关系映射).入门门槛较高的,不需要写sql,sql语句自动生成了.对sql语句进行优化.修改比较困 ...

  9. Mybatis第六篇【配置文件和映射文件再解读、占位符、主键生成与获取、Mapper代理】

    配置文件和映射文件再解读 映射文件 在mapper.xml文件中配置很多的sql语句,执行每个sql语句时,封装为MappedStatement对象,mapper.xml以statement为单位管理 ...

随机推荐

  1. Spring 面试详解

    SpringSpring就像是整个项目中装配bean的大工厂,在配置文件中可以指定使用特定的参数去调用实体类的构造方法来实例化对象.Spring的核心思想是IoC(控制反转),即不再需要程序员去显式地 ...

  2. leetcode125. 验证回文串 python 简单

    125. 验证回文串 难度简单     给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写. 说明:本题中,我们将空字符串定义为有效的回文串. 示例 1: 输入: &quo ...

  3. git和github入门指南(6)

    6.交作业的流程 以下内容是螺钉课堂在线就业班提交作业的要求,非螺钉课堂在线就业班学员不用学习 螺钉课堂作业全程采用git管理,希望在日常使用中,加深对git和github的理解 具体流程: 1.注册 ...

  4. 【部分】@using (Html.BeginForm())参数示例

    原文:https://www.cnblogs.com/firstcsharp/p/3238321.html 1.指定表单提交方式和路径等 @using (Html.BeginForm("In ...

  5. 全网最深分析SpringBoot MVC自动配置失效的原因

    前言 本来没有计划这一篇文章的,只是在看完SpringBoot核心原理后,突然想到之前开发中遇到的MVC自动失效的问题,虽然网上有很多文章以及官方文档都说明了原因,但还是想亲自看一看,本以为很简单的事 ...

  6. 报错 500 - Request processing failed; nested exception is com.alibaba.dubbo.rpc.RpcException的解决放案

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/loveliness_peri/artic ...

  7. List集合-01.ArrayList

    1.ArrayList 1.1 实现了Access接口 实现标记接口Access有以下特点: 目的是允许通用算法提供良好的性能 当遍历方式不同,速度不同时,通常需要继承这个接口 1.2 ArrayLi ...

  8. web前端知识点(JavaScript篇)

    call,apply,bind call,apply,bind这三者的区别,及内部实现原理,点这里 promise promise函数的内部实现原理,点这里 闭包 闭包就是能够读取其他函数内部变量的函 ...

  9. 神奇的建图方式(Tarjan)——小z玩游戏

    原题来自与:洛谷 P5676(GZOI2017)  链接: https://www.luogu.com.cn/problem/P5676 题面: 题意比较明显,如果已经建好了边,那么跑个Tarjan ...

  10. python中获取文件路径的几种方式

    # 如果执行文件为E:\aa\bb\aa.py 1.获取当前路径 current_path11 = os.path.abspath(__file__) current_path12 = os.path ...