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为单位管理 ...
随机推荐
- android面试详解
前台就是和用户交互的进程 可见进程例如一个activity被一个透明的对话框覆盖,该activity就是可见进程 服务:service进程 后台一个activity按了home按键就是从前台退回到后台 ...
- Python 简明教程 --- 15,Python 函数
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 测试只能证明程序有错误,而不能证明程序没有错误. -- Edsger Dijkstra 目录 本节我 ...
- keras中loss与val_loss的关系
loss是训练集的损失值,val_loss是测试集的损失值 以下是loss与val_loss的变化反映出训练走向的规律总结: train loss 不断下降,test loss不断下降,说明网络仍在学 ...
- SpringBoot中VO,DTO,DO,PO的概念、区别和用处
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/zhuguang10/article/de ...
- BZOJ 3573米特运输
Description 米特是D星球上一种非常神秘的物质,蕴含着巨大的能量.在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题.D星上有N个城市,我们将其顺序编号为1到N,1号城市为 ...
- 手写SpringMVC框架(二)-------结构开发设计
续接前文, 手写SpringMVC框架(一)项目搭建 本节我们来开始手写SpringMVC框架的第二阶段:结构开发设计. 新建一个空的springmvc.properties, 里面写我们要扫描的包名 ...
- springboot Jar包开启远程调试
jar 正常启动 java -jar rest-demo-0.0.1-SNAPSHOT.jar 开启DEBUG模式需追加参数 java -Xdebug -Xrunjdwp:server=y,trans ...
- HDU3686 Traffic Real Time Query System 题解
题目 City C is really a nightmare of all drivers for its traffic jams. To solve the traffic problem, t ...
- Emergency Evacuation,题解
题目: 题意: 在某一秒,每个人可以进行一个移动:去旁边座位,去过道,在过道向出口走,求最少多少秒可以让所有人离开(具体如图和样例). 分析: 首先,我们先考虑简单的,只考虑出口前有什么事件发生:1. ...
- 通过注入DLL修改API代码实现钩取(一)
通过注入DLL修改API代码实现钩取(一) Ox00 大致思路 通过CreateRemoteThread函数开辟新线程,并将DLL注入进去 通过GetProcessAddress函数找到需钩取的API ...