5、MyBatis-parameterType 入参封装 Map 流程
以如下入参为例,MyBatis 版本为 3.5.0
public MyUser selectMyUserIdAndAge(Integer id, @Param("user") MyUser user);
打上断点

大致流程
1、进入到 MapperProxy 类的 invoke 方法,执行接口的代理对象中的方法
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 代理以后,并不是所有 Mapper 的方法调用时都会调用这个 invoke 方法,如果这个方法是 Object 中通用的方法(toString、hashCode等)则无需执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
// 如果这个方法的权限修饰符是 public 并且是由接口提供,则执行 invokeDefaultMethod 方法,这里是由代理对象提供
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 去缓存中找 MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC && method.getDeclaringClass().isInterface();
}
}
2、进入到 MapperMethod 类的 execute 方法,执行数据库操作
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 4种情况,insert|update|delete|select,分别调用 SqlSession 的 4 大类方法
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// 如果有结果处理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 如果结果有多条记录
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 如果结果是map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 如果结果是游标
result = executeForCursor(sqlSession, args);
} else {
// 否则就是一条记录
// 装配参数
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
...
}
3、进入到 ParamNameResolver 类的 getNamedParams 方法,进行参数封装
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
public ParamNameResolver(Configuration config, Method method) {
// 获取参数列表中每个参数的类型
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取每个标了 @Param 注解的参数的值,赋值给 name
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 记录参数索引与参数名称的关系
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// 遍历参数保存至 map:(key:参数索引,value:name的值)
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 是否为特殊参数:RowBounds或ResultHandler
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
// 标注了 @Param 注解:name的值为注解的值
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
// 没有标 @Param 注解
if (name == null) {
if (config.isUseActualParamName()) {
// 全局配置:useActualParamName(jdk1.8):name为参数名
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// name为map.size():相当于当前元素的索引,0,1,2,3...
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
private String getActualParamName(Method method, int paramIndex) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}
private static boolean isSpecialParameter(Class<?> clazz) {
return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}
public String[] getNames() {
return names.values().toArray(new String[0]);
}
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
// 没有参数
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 未使用@Param注解且参数列表只有一个,即 args[0] 参数的值
return args[names.firstKey()];
} else {
// 为参数创建 param+ 索引的格式作为默认参数名称 如:param1 下标从1开始
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 遍历names集合;{0=arg0, 1=user}
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// names集合的 value 作为 key; names 集合的 key 作为取值的参考args[0]
// {arg0:args[0],user=args[1]}
param.put(entry.getValue(), args[entry.getKey()]);
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
if (!names.containsValue(genericParamName)) {
// 额外的将每一个参数也保存到 param 中,使用新的key:param1...paramN,有 @Param 注解可以 #{指定的key},或者#{param1}
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
https://my.oschina.net/u/3737136/blog/1811654
https://segmentfault.com/a/1190000015977801
https://github.com/tuguangquan/mybatis/tree/master/src/main/java/org/apache/ibatis/binding
5、MyBatis-parameterType 入参封装 Map 流程的更多相关文章
- mybatis框架之多参数入参--传入Map集合
需求:查询出指定性别和用户角色列表下的用户列表信息 实际上:mybatis在入参的时候,都是将参数封装成为map集合进行入参的,不管你是单参数入参,还是多参数入参,都是可以封装成map集合的,这是无可 ...
- MyBatis(入参的类型和日志记录)
入参的类型是对象 1. 新增的参数是对象 2. 空值的处理,占位符 字段,jdbcType=VARCHAR 字符串 字段,jdbcType=DATE ...
- MyBatis对入参对象的属性空判断
<!-- 查询学生list,like姓名 --> <select id="getStudentListLikeName" parameterType=&q ...
- Mybatis方法入参处理
1,在单个入参的情况下,mybatis不做任何处理,#{参数名} 即可,甚至连参数名都可以不需要,因为只有一个参数,或者使用 Mybatis的内置参数 _parameter. 2,多个入参: 接口方法 ...
- 先查询再插入,改为存储过程,java部分入参出参、mybatisxml【我】
先查询再插入,改为存储过程 create or replace procedure PRO_REVENUE_SI(l_p_cd in Varchar2, l_c_cd in Varchar2, l_p ...
- springMVC使用map接收入参 + mybatis使用map 传入查询参数
测试例子: controllel层 ,使用map接收请求参数,通过Debug可以看到,请求中的参数的值都是字符串形式,如果将这个接收参数的map直接传入service,mybatis接收参数时会报错, ...
- 关于用mybatis调用存储过程时的入参和出参的传递方法
一.问题描述 a) 目前调用读的存储过程的接口定义一般是:void ReadDatalogs(Map<String,Object> map);,入参和出参都在这个map里 ...
- MyBatis版本升级导致OffsetDateTime入参解析异常问题复盘
背景 最近有一个数据统计服务需要升级SpringBoot的版本,由1.5.x.RELEASE直接升级到2.3.0.RELEASE,考虑到没有用到SpringBoot的内建SPI,升级过程算是顺利.但是 ...
- Mybatis调用PostgreSQL存储过程实现数组入参传递
注:本文来源于 < Mybatis调用PostgreSQL存储过程实现数组入参传递 > 前言 项目中用到了Mybatis调用PostgreSQL存储过程(自定义函数)相关操作,由于Pos ...
随机推荐
- POJ 2112-Optimal Milking-二分答案+二分图匹配
此题有多种做法. 使用floyd算法预处理最短路,二分答案,对于每一个mid,如果距离比mid小就连边, 注意把每个机器分成m个点.这样跑一个最大匹配,如果都匹配上就可以减小mid值. 用的算法比较多 ...
- Codeforces1037G A Game on Strings 【SG函数】【区间DP】
题目分析: 一开始没想到SG函数,其它想到了就开始敲,后来发现不对才发现了需要SG函数. 把每个字母单独提出来,可以发现有用的区间只有两个字母之间的区间和一个位置到另一个字母的不跨越另一个相同字母的位 ...
- UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】
题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一 ...
- Quartus prime16.0 与modelsim ae 联调
前言 quartus和modelsim联调对仿真还是很方便的,当然最好是quartus干综合到烧录的活,modelsim单独仿真.而且ae版的性能比se版差. 流程: 1.配置modelsim ae路 ...
- pip详解
pip是一个安装和管理 Python 包的工具.python安装包的工具有easy_install, setuptools, pip,distribute等,pip是Python官方推荐的包管理工具 ...
- 【LOJ6036】编码(2-sat)
[LOJ6036]编码(2-sat) 题面 LOJ 题解 很显然的一个暴力: 枚举每个串中的?是什么,然后把和它有前缀关系的串全部给找出来,不合法的连边处理一下,那么直接跑\(2-sat\)就做完了. ...
- HDU 4549 M斐波那契数列(矩阵快速幂)
题目链接:M斐波那契数列 题意:$F[0]=a,F[1]=b,F[n]=F[n-1]*F[n-2]$.给定$a,b,n$,求$F[n]$. 题解:暴力打表后发现$ F[n]=a^{fib(n-1)} ...
- 牛客练习赛43 Tachibana Kanade Loves Game (简单容斥)
链接:https://ac.nowcoder.com/acm/contest/548/F来源:牛客网 题目描述 立华奏是一个天天打比赛的萌新. 省选将至,萌新立华奏深知自己没有希望进入省队,因此开始颓 ...
- iptables防火墙详解(三)
linux 高级路由 策略路由(mangle表) lartc(linux advanced routing and traffic control) http://www.lartc.org # rp ...
- 在linux环境下搭建JDK+JAVA+Mysql,并完成jforum的安装
参考链接: YUM安装MySQL和JDK和Tomcat:http://cmdschool.blog.51cto.com/2420395/1696206/ http://www.cnblogs.com/ ...