单元测试:
SqlSession openSession = sqlSessionFactory.openSession(); EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class); Employee employee = mapper.getEmpByIdAndLastName(1, "Tom");第三行在执行断点调试时, 先来到一个名字叫mapperProxy.class
动态代理的InvocationHandler
public class MapperProxy<T> implements InvocationHandler, Serializable {}
然后看里面的invoke方法
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //有一个判断,当前方法声明的类是在object里面声明的, 直接放行. if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //否则,把method包装成一mapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
下面看看如何执行的, 我们断点进入execute
public Object execute(SqlSession sqlSession, Object[] args) { Object result; //执行之前,先判断是什么类型的,对应的走增删改查的方法 //每次调用之前,resulte就是返回值,会把你传过来的参数转化为sql能用的. 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);//返回单个对象 //底层调用的还是sqlSession 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; }看上面的代码, Object param=method.convertArgsToSqlCommandParam(args);
这个里面是:
public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); }我们跳进来看, 会发现这么一个方法:getNamedParams
这一段就是处理的代码, 我们看到param1,param2.这一块就是把原来的值封装成了param1和param2
ParamNameResolver解析参数封装map的, 这一块的代码我们来大致走一走
/** * <p> * A single non-special parameter is returned without a name.<br /> * Multiple parameters are named using the naming rule.<br /> * In addition to the default names, this method also adds the generic names (param1, param2, * ...). * </p> */ public Object getNamedParams(Object[] args) { //上手先获取了一个names.size(),而这个names里面是有值的:{0=id,1=lastName}, key就是0,1 value就是id和lastName. 这里我们就能看出来是调用的哪个mapper接口, 见下图. 那name是如何确定的,我们见图下面的分析 final int paramCount = names.size(); ........... }
这个name是我们ParamNameResolver的一个参数
private final SortedMap<Integer, String> names;而这个参数值的确定, 我们来看这个类的构造器
1.获取每个标了param注解的param值:id ,lastname; 赋值给name
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
name的值: 标注了param注解:注解的值
没有标注:
1>全局配置:useActualParamName(jdk1.8):name=参数名 2>name=map.size();相当于当前元素的索引,假如传入了第三个,没标注解,那就key是2,value也是2了{0=id, 1=lastName,2=2}
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++) { if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) {//如果当前参数的注解是param hasParamAnnotation = true; name = ((Param) annotation).value();//拿到param注解的value值 break; } } if (name == null) { // @Param was not specified. if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex);//Jdk1.8 } if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size());//没标注解,name就是map的size } } map.put(paramIndex, name);//map每确定一个参数,就会增大一下 } names = Collections.unmodifiableSortedMap(map); }我们接着来看getNamedParams方法
参数args 是[1,"Tom"]
public Object getNamedParams(Object[] args) { final int paramCount = names.size(); //参数为null,直接返回 if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { //如果只有一个元素,并且没有Param注解;args[0]: 单个参数直接返回 return args[names.firstKey()]; } else { //多个元素或者有Param标注 final Map<String, Object> param = new ParamMap<Object>(); int i = 0; //map保存数据,最终是要返回的 //遍历names,names我们上面讲了, 在构造器的时候就确定好了 {0=id,1=lastName} for (Map.Entry<Integer, String> entry : names.entrySet()) { //name集合的value作为key;names集合的key又作为取值的参考args[0]:args[1,"Tom"] //最终的效果: {id=args[0]:1,lastName=args[1]:Tom} param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) //额外的将每一个参数也保存到map中,使用新的key:param1...paramN //效果:有Param注解可以#{指定的key},或者#{param1} 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; } }
到这里,就ok了, 转成param1, value 了. 参数传递也就ok啦!!
这是多个参数写了param的,如果没有写的话, param是一个map类型, 那么key就会是它的序号作为名字了,所以建议多个参数的时候使用param,不然后面如果顺序反了,就会错了.方便动态sql使用吧.
另外还有一个,就是参数如果传的是list. 只有一个参数, 通常也就不适用param了.
我们来看一下源码
if (object instanceof List) { StrictMap<Object> map = new StrictMap<Object>(); map.put("list", object); return map; }
所以取的时候,应该是collection="list"
如果不用param呢,应该怎么写:
@Select("select * from t_sign where is_delete=#{arg2} AND status=#{arg0} AND id=#{arg1}") // SignEntity selectBySignId(@Param("signId") String signId,@Param("status") String status); SignEntity selectBySignId( String status ,String signId,String delete);
我测试了好几种,最后得出的结论是: 取值必须得arg[i]来取,因为我们看过是上面的源码, 它的key值是索引. 并且取值顺序依赖的是:接口参数的顺序.
所以如果不用param的话, 我理解的是代码可读性不高,况且太过于依赖参数的顺序,不方便维护.
好了好了,就到这里了