一、背景
群里有个哥们分享了一个mybatis的小”坑“。
”分享一个菜鸡点:mybatis中使用@param注解后,要keyProperty=“注解名.id”,不然拿不到生成的主键值“
那么我们就要看@Param 在什么时候用?为啥不写参数名不行呢?
二、解析
2.1 官方文档大法
官方文档http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
user类
代码语言:javascript复制public class User {
//...
public User(Integer id, String username, int age) {
//...
}
//...
}
为了将结果注入构造方法,MyBatis 需要通过某种方式定位相应的构造方法。 在下面的例子中,MyBatis 搜索一个声明了三个形参的的构造方法,参数类型以 java.lang.Integer, java.lang.String 和 int 的顺序给出。
代码语言:javascript复制
当你在处理一个带有多个形参的构造方法时,很容易搞乱 arg 元素的顺序。
从版本 3.4.3 开始,可以在指定参数名称的前提下,以任意顺序编写 arg 元素。
为了通过名称来引用构造方法参数,你可以添加 @Param 注解,或者使用 '-parameters' 编译选项并启用 useActualParamName 选项(默认开启)来编译项目。
下面是一个等价的例子,尽管函数签名中第二和第三个形参的顺序与 constructor 元素中参数声明的顺序不匹配。
代码语言:javascript复制
如果存在名称和类型相同的属性,那么可以省略 javaType 。剩余的属性和规则和普通的 id 和 result 元素是一样的。
【1】什么情况下用@param注解
一、是参数的顺序和xml映射文件的顺序不匹配时。
二、是参数为两个对象且属性有重名的时候,mybatis无法感知是哪个类的属性。
2.2 源码大法
为什么设置了@Parm的值,keyProperty不加item前缀就不生效呢?(内容略长,要有心理准备哈哈)
源码参考这里
我们加注解
代码语言:javascript复制void insert(@Param("item") CuxTodoItems cuxTodoItems);
对应xml
代码语言:javascript复制 insert into cux_todo_items (user_id,todo_item_title,todo_item_content,priority)
values ( #{item.userId},#{item.todoItemTitle},#{item.todoItemContent},#{item.priority});
先执行这里构造参数名解析对象
代码语言:javascript复制 public ParamNameResolver(Configuration config, Method method) {
final Class[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap map = new TreeMap();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex ) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
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);
}
执行插入语句需要执行到
org.apache.ibatis.binding.MapperMethod#execute
代码语言:javascript复制public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
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;
}
代码语言:javascript复制public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
底层调用
org.apache.ibatis.reflection.ParamNameResolver#getNamedParams
代码语言:javascript复制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 {
final Map param = new ParamMap();
int i = 0;
for (Map.Entry 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;
}
}
我们看到参数名解析这一块,如果有@param注解则取这个注解的值,否则根据参数的索引取参数名,如果取不到则映射为0,1...
然后调用插入后
执行对GeneratedKeys的处理
org.apache.ibatis.executor.keygen.SelectKeyGenerator#processGeneratedKeys
org.apache.ibatis.executor.statement.PreparedStatementHandler#update
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processAfter
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBatch
最重要的是两个函数
第一个是:org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#getTypeHandlers
判断keyProperties是否存在,如果存在类型是啥
这里存在且类型为Integer
执行org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#populateKeys填充完毕
项目源码:git@github.com:chujianyun/MyBatisParam.git
调用Insert的测试url: http://localhost:8080/MyBatisParam/cuxTodoItems/insert?userId=2&todoItemTitle=测试&todoItemContent=2&priority=1
发现核心就是用了@Parm设置了名字为item后,如果只是设置基本属性不带item的话,从ObjectWrapper里找不到这个属性。
如果不用注解啥情况呢?
insert into cux_todo_items (user_id,todo_item_title,todo_item_content,priority)
values ( #{userId},#{todoItemTitle},#{todoItemContent},#{priority});
设置参数名解析
org.apache.ibatis.reflection.ParamNameResolver#ParamNameResolver
插入后调用获取类型解析器
不设置@Param注解时,objectWrapper直接是一个Object,所以直接可以找到todoItemId属性。
这是关键。
调用Url:http://localhost:8080/MyBatisParam/cuxTodoItems/insert2?userId=2&todoItemTitle=测试&todoItemContent=2&priority=1
三、总结
遇到问题优先看官方文档,一般大多数用法都可以找到。
另外通过源码调试时学习的一个非常重要方法,大家遇到类似问题可以通过调试来研究。
如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。
另外欢迎加入我的知识星球,知识星球ID:15165241 一起交流学习。
https://t.zsxq.com/Z3bAiea 申请时标注来自CSDN。