Mybatis——Mapper代理
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代理的更多相关文章
- mybatis入门基础(二)----原始dao的开发和mapper代理开发
承接上一篇 mybatis入门基础(一) 看过上一篇的朋友,肯定可以看出,里面的MybatisService中存在大量的重复代码,看起来不是很清楚,但第一次那样写,是为了解mybatis的执行步骤,先 ...
- Spring+SpringMVC+Mybatis大整合(SpringMVC采用REST风格、mybatis采用Mapper代理)
整体目录结构: 其中包下全部是采用mybatis自动生成工具生成. mybatis自动生成文件 <?xml version="1.0" encoding="UTF- ...
- mybatis——使用mapper代理开发方式
---------------------------------------------------------------generatorConfig.xml------------------ ...
- MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析
我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...
- mybatis入门-mapper代理原理
原始dao层开发 在我们用mybatis开发了第一个小程序后,相信大家对于dao层的开发其实已经有了一个大概的思路了.其他的配置不用变,将原来的test方法,该为dao的方法,将原来的返回值,直接在d ...
- mybatis系列笔记(2)---mapper代理方法
mapper代理方法 在我们在写MVC设计的时候,都会写dao层和daoimp实现层,但假如我们使用mapper代理的方法,我们就可以不用先daoimp实现类 当然这得需要遵守一些相应的规则: (1) ...
- Spring+SpringMVC+MyBatis深入学习及搭建(二)——MyBatis原始Dao开发和mapper代理开发
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6869133.html 前面有写到Spring+SpringMVC+MyBatis深入学习及搭建(一)——My ...
- 用mybatis实现dao的编写或者实现mapper代理
一.mybatis和hibernate的区别和应用场景hibernate:是一个标准的ORM框架(对象关系映射).入门门槛较高的,不需要写sql,sql语句自动生成了.对sql语句进行优化.修改比较困 ...
- Mybatis第六篇【配置文件和映射文件再解读、占位符、主键生成与获取、Mapper代理】
配置文件和映射文件再解读 映射文件 在mapper.xml文件中配置很多的sql语句,执行每个sql语句时,封装为MappedStatement对象,mapper.xml以statement为单位管理 ...
随机推荐
- jni不通过线程c回调java的函数 --总结
1.JNIEnv类型是一个指向全部JNI方法的指针.该指针只在创建它的线程有效,不能跨线程传递 2.JavaVM是虚拟机在JNI中的表示,一个JVM中只有一个JavaVM对象,这个对象是线程共享的. ...
- Struts2 执行流程 以及 Action与Servlet比较 (个人理解)
上图提供了struts2的执行流程.如下: 1:从客户端发出请求(HTTPServletRequest). 2:请求经过各种过滤器(filter),注:一般情况下,如SiteMesh等其他过滤器要放在 ...
- python 3内置函数
2018-07-14 enumerate() 用于指定下标 例: m = ['a','b','c'] for i,j in enumerate(m,1): print(i,j) 输出: 1 a 2 b ...
- python+opencv图像增强——拉普拉斯
img = cv2.imread(r'F:\python\work\cv_learn\clipboard.png',1) cv2.imshow('input',img) kernel = np.arr ...
- vue基础入门(3)
3.组件基础 3.1.什么是组件? 3.1.1.理解组件 前端组件化开发是目前非常流行的方式,什么是前端组件化开发呢?就是将页面的某一部分独立出来,将这一部分的数据.视图.以及一些控制逻辑封装到一个组 ...
- vue基础入门(2.1)
2.vue基础用法 2.1.事件处理 2.1.1.监听事件 使用v-on:事件名称 = '事件处理函数'的形式来监听事件,事件处理函数要写在methods后面的对象中 <!DOCTYPE htm ...
- ORA-04063: package body "DBSNMP.BSLN_INTERNAL" has errors
ORA-04063: package body "DBSNMP.BSLN_INTERNAL" has errors 问题描述: 警告日志出现报错: Sun Jun 21 00:00 ...
- Azure Data Box
一,引言 最近博主又要面临考试了,随笔又再次缓慢更新,说起考试,之前在微软的 ms learn的课程上有接触到一个叫 Azure Data Box的,刚好今天也花了一个多小时看了一下相关文档,下面就正 ...
- 资深前端工程师带你认识网页后缀html、htm、shtml、shtm有什么区别?
每一个网页或者说是web页都有其固定的后缀名,不同的后缀名对应着不同的文件格式和不同的规则.协议.用法,最常见的web页的后缀名是.html和.htm,但这只是web页最基本的两种文件格式,今天我们来 ...
- Jmeter(十五) - 从入门到精通 - JMeter导入自定义的Jar包(详解教程)
1.简介 原计划这一篇是介绍前置处理器的基础知识的,结果由于许多小伙伴或者童鞋们在微信和博客园的短消息中留言问如何引入自己定义的Jar包呢???我一一回复告诉他们和引入插件的Jar包一样的道理,一通百 ...