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为单位管理 ...
随机推荐
- 在maven项目中使用Junit进行单元测试(一)
https://blog.csdn.net/ai_xue_xi/article/details/51819729 这篇文章相当的经典,最好使用的maven生成单元测试报告,不要在使用ant脚本生成单元 ...
- linux网络编程-socket(36)
进程是程序的一次动态执行的过程,进程是短暂的. 一个程序可以对应多个进程,可以打开多个记事本程序,存在多个进程. 线程是进程内部中的控制序列,一个进程至少有一个执行线路. 一个进程可以存在多个线程
- PHP 多维数组转json对象
PHP 多维数组转json对象 php 数组转json对象,可能大家都知道要用json_encode,但是转换出来的格式多有不同,此处做个小小的记录! 1. 一维数组转json对象 <?php ...
- SQL注入入门
这几天做了不少SQL注入题,对SQL注入有点体会,所以写写自己的学习历程与体会. 什么是SQL注入 SQL注入就是指web程序对用户输入的数据的合法性没有进行判断,由前端传入的参数带着攻击者控制的非法 ...
- 07 . Prometheus监控Memcached并配置Grafana
List CentOS7.3 prometheus-2.2.1.linux-amd64.tar.gz redis_exporter-v0.30.0.linux-amd64.tar.gz ` 节点名 I ...
- java方法中开启一个线程
很多业务场景下需要你在一个方法中去开启一个线程,去跑一些处理时间较长的代码,这样调用方就不必经过长时间的等待了.好了 话不多说 先上代码: package test; public class Th ...
- P3879 阅读理解
都这么大了,you这些怎么能算生词呢,难道三年级以前就有人做蓝题了吗(是我不配) 我觉得这道题出难一点点的话,可以整行读入什么的(口嗨怪).先看题目,对于每个生词,输出他出现在了哪些文章(需要排序). ...
- 六.url配置
1.Django 如何处理一个请求 (1). django 加载 ROOT_URLCONF(settings.py中配置的) 指定的模块,并寻找可用的urlpatterns变量.它是 django.c ...
- 【Java8新特性】冰河带你看尽Java8新特性,你想要的都在这儿了!!(文本有福利)
写在前面 很多小伙伴留言说,冰河你能不能写一些关于Java8的文章呢,看书看不下去,看视频进度太慢.好吧,看到不少读者对Java8还是比较陌生的,那我就写一些关于Java8的文章吧,希望对大家有所帮助 ...
- SimpleImputer 中fit和transform方法的简介
sklearn.impute.SimpleImputer 中fit和transform方法的简介 SimpleImputer 简介 通过SimpleImputer ,可以将现实数据中缺失的值通过同一列 ...