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为单位管理 ...
随机推荐
- JavaWeb网上图书商城完整项目--过滤器解决中文乱码
我们知道,如果是POST请求,我们需要调用request.setCharacterEncoding(“utf-8”)方法来设计编码:如果是GET请求,我们需要自己手动来处理编码问题.如果我们使用了En ...
- mpvue实战-手势滑动导航栏
写点东西记录一下美好时光,上周学习了一下通过mpuve开发微信小程序,看完文档,就准备撸起袖子加油干的时候,一开始就被支持手势滑动的导航栏给搞懵逼了.求助一波百度和谷歌未果后,只能自己动脑动手!为了给 ...
- 重学 Java 设计模式:实战观察者模式「模拟类似小客车指标摇号过程,监听消息通知用户中签场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 知道的越多不知道的就越多 编程开发这条路上的知识是无穷无尽的, ...
- Layer 3.0
https://jeesite.gitee.io/front/layer/3.0/layer.layui.com/index.html
- 每天一个Linux命令(mkdir)
每天一个Linux命令(mkdir) mkdir: /bin/mkdir,创建目录( make directories)语法:mkdir [选项]... 不存在的目录...目录:默认时必须该目录不存在 ...
- 探讨NET Core数据进行3DES加密或解密弱密钥问题
前言 之前写过一篇<探讨.NET Core数据进行3DES加密和解密问题>,最近看到有人提出弱密钥问题,换个强密钥不就完了吗,猜测可能是与第三方对接导致很无奈不能更换密钥,所以产生本文解决 ...
- django 缓存(memcached)
Django提供了6种缓存方式 开发调试缓存 内存缓存 文件缓存 数据库缓存 Memcache缓存(使用python-memcached模块) Memcache缓存(使用pylibmc模块) 常使用的 ...
- P4408 逃学的小孩 题解
题目描述 Chris家的电话铃响起了,里面传出了Chris的老师焦急的声音:"喂,是Chris的家长吗?你们的孩子又没来上课,不想参加考试了吗?"一听说要考试,Chris的父母就心 ...
- JSR 303 进行后台数据校验
一.JSR 303 1.什么是 JSR 303? JSR 是 Java Specification Requests 的缩写,即 Java 规范提案. 存在各种各样的 JSR,简单的理解为 JSR 是 ...
- (一)学习了解OrchardCore笔记——开篇:基于asp.net core的OrchardCore
想深入了解OrchadCore源码许久了,但是读源码的时候遇到很多问题而网上的参考资料太少了(几乎都是OrchadCms不带OrchardCore的),现在解决得差不多了,做下笔记方便自己查看,有错误 ...