内容简介:代码如下:Mapper接口中的代码:MapperXML中的SQL代码:
代码如下:
Mapper接口中的代码:
List<String> queryTransCdByType(String type); 复制代码
MapperXML中的 SQL 代码:
<select id="queryTransCdByType" resultType="String" parameterType="String"> SELECT * FROM 表名 t where 1=1 <if test="type!=''"> and t.type = #{type} </if> </select> 复制代码
单元测试进行测试SQL的执行情况时,报了如下的错误:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'type' in 'class java.lang.String' 复制代码
报错是在接口中没有加 @Param
参数进行参数的命名,所以在SQL中使用动态SQL时候找不到 key=type
的值,所以报了错误。关于不知道什么是动态SQL的可以看动态 SQL官网的介绍
2. 源码分析找错误
于是上网查了许多的资料,关于为什么会报这个错误,进行了如下的整理。
在MyBatis源码中 MapperMethod.java
会首先经过下面方法来转换参数:
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<String, Object> param = new ParamMap<>(); 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; } } 复制代码
在这里有个很关键的 names
,这个参数类型为 Map<Integer, String>
,他会根据接口方法按顺序记录下接口参数的定义的名字,如果使用 @Param
指定了名字,那么就会将就会记录这个名字,用 Map
进行记录,key是用 GENERIC_NAME_PREFIX + String.valueOf(i + 1);
生成的。value是 @Param
指定的名字。
GENERIC_NAME_PREFIX
的定义如下:
private static final String GENERIC_NAME_PREFIX = "param"; 复制代码
例如在接口中如下的传参形式:
List<String> queryTransCdByType(String type); 复制代码
那么它的 names
的属性是
如果是如下的传参形式:
List<String> queryTransCdByType(@Param("type") String type); 复制代码
那么它的 names
的属性是
hasParamAnnotation
参数表示是是否在传参中使用的 @Param
注解
继续看上面的 getNamedParams
方法,有以下的三种情况
-
当传入的空参数的时候,那么返回的是null
-
如果没有
@Param
注解的时候,那么直接返回的就是所传的参数的值 -
如果有
@Param
,注解的话,那么返回的是Map- 举例如果是
List<String> queryTransCdByType(@Param("type") String type);
,那么Map的组成如下
- 举例如果是
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); //如果参数含有rowBounds则调用分页的查询 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; } 复制代码
得到了参数以后就会调用下面这段语句
result = sqlSession.<E>selectList(command.getName(), param); 复制代码
进入 selectList()
方法以后代码如下
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } 复制代码
继续往下debug执行,我们会看到 DynamicContext
这个类
public DynamicContext(Configuration configuration, Object parameterObject) { if (parameterObject != null && !(parameterObject instanceof Map)) { MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject); } else { bindings = new ContextMap(null); } bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId()); } 复制代码
其中 Object parameterObject
参数是我们一开始处理的参数,有一下的三种情况
@Param
如果是1和2情况的话,那么就会进入
MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject); 复制代码
此时我们进去 configuration.newMetaObject(parameterObject)
里面,会发现最后返回 MetaObject
return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory); 复制代码
MetaObject是MyBatis的一个反射类,可以很方便的通过getValue方法获取对象的各种属性(支持集合数组和Map,可以多级属性点.访问
此时我们再往下看,会发现有一个 ContextMap
静态内部类,代码如下
static class ContextMap extends HashMap<String, Object> { private static final long serialVersionUID = 2977601501966151582L; private MetaObject parameterMetaObject; public ContextMap(MetaObject parameterMetaObject) { this.parameterMetaObject = parameterMetaObject; } @Override public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { // issue #61 do not modify the context when reading return parameterMetaObject.getValue(strKey); } return null; } } 复制代码
可以看到,如果是1和2情况的话,那么 this.parameterMetaObject
不是空,如果是3情况的话,那么 this.parameterMetaObject
就是Null。
再接着从 DynamicContext
类往下看
bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId()); ----- public static final String PARAMETER_OBJECT_KEY = "_parameter"; public static final String DATABASE_ID_KEY = "_databaseId"; 复制代码
此时可以知道在 bindings
Map中key为 _parameter
的value是之前我们解析出来的参数,不管是Map类型还是Object类型全都在里面。当随后进行解析从 ContextMap
中取值的时候会调用 ContextAccessor
类下面的方法
public Object getProperty(Map context, Object target, Object name) throws OgnlException { Map map = (Map) target; Object result = map.get(name); if (map.containsKey(name) || result != null) { return result; } Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) { return ((Map)parameterObject).get(name); } return null; } 复制代码
Object target
就是我们之前封装的 bindings
参数。 Object name
是经过解析的在xml中if标签中的值,此时 name=type
我们会先看到他直接从map中取值,进去以后代码如下
@Override public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { // issue #61 do not modify the context when reading return parameterMetaObject.getValue(strKey); } return null; } } 复制代码
此时有三种情况。
parameterMetaObject parameterMetaObject
此时如果是返回了null会继续下面的代码
if (map.containsKey(name) || result != null) { return result; } Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) { return ((Map)parameterObject).get(name); } 复制代码
因为返回的result是null,所以执行到
Object parameterObject = map.get(PARAMETER_OBJECT_KEY); 复制代码
从map中拿到 _parameter
的值。如果他是map的话,那么再从map中取出所解析出来的值。
3. 总结
其实这个问题如果不深究的话也很好解决,直接粘贴错误信息到百度上直接查找就会知道是怎么错了。但是还是想知道为什么会报这个错,在深究这个错误期间,也学习了很多,里面运用了许多的设计模式,也借着这个机会学会了代理模式,和组合模式。第一次写关于源码解析的文章,借鉴了许多大佬的东西,也加入了自己的见解。如有不足,请多指出。
4. 参考文章
以上所述就是小编给大家介绍的《不学无数 — 记一次常见异常而导致的Debug源码之旅》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Spring Cloud微服务实战
翟永超 / 电子工业出版社 / 2017-5 / 89
《Spring Cloud微服务实战》从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 《Sp......一起来看看 《Spring Cloud微服务实战》 这本书的介绍吧!