源码分析 There is no getter for property named '*' in 'class java.lang.String
2017-03-06 17:55
507 查看
转自:http://blog.csdn.net/qing_gee/article/details/47122227
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
该sql对应的mapper class中对应的方法为
那么这个时候,项目运行该查询语句时,就会抛出
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
mybatis-3.2.3-sources.jar
mybatis-spring-1.2.2-sources.jar
当然了,你项目中对应的lib包也是相应的版本。
然后,我们把对应的源码进行反编译,生成对应的source,使用的工具是jd-gui.exe。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/26/69b8c2b9bd28d6153a69f1f215f31f2c)
紧接着,我们来看看如何关联源码包,见下图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/26/e0d9eb24385e6c11e39cd757b0fae6f3)
我已经加载好了,如果是首次的话,可点击edit,在弹出的提示框中选择上一步保存的zip文件。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/26/88abd726e9f7fe77e7df2307324997de)
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
我们在
2
3
4
5
6
7
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
[/code]
可以尾随debug进入到MapperMethod.java
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[/code]
进入到该方法后,可以一直调试到
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/26/d3073531a7d92fb176391292221181cc)
在弹出框中选择open implementation,然后进入到DefaultSqlSession.java
2
3
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
[/code]
在
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
tips:猫腻就在
(…)(省略步骤,个人调试过程中请注意。)
直到你进入到DynamicContext.java类时
2
3
4
5
6
7
8
9
10
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
[/code]
此时,你不妨wait a moment,翻看一下该类的整体代码,你会发现:
2
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
[/code]
这里有两个常量,当然了,但看此处,也许你会发现
key1:_parameter
(…)(省略步骤,个人调试过程中请注意。)
然后,我们进入MixedSqlNode.java
2
3
4
5
6
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
[/code]
该apply方法就非常有意思了,xml里配置的sql语句,会通过该方法转换为标准的sql(称之为标准,是值这形成的sql语句就是能够执行预处理sql查询的字符串),你不妨慢一点执行该循环语句。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/26/3bcc99258fe6ab36e576daa8f372e4e8)
第二次循环的时候,你就可以看到sql的雏形了,那么请继续。
(…)(省略步骤,个人调试过程中请注意。)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/26/8d750c264036d0b2a978b346a8c8618a)
直到你发现,sqlNode的类型为ChooseSqlNode,此时,你是否已经能联想到以下内容:
2
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
[/code]
事情开始变得明朗起来,真好。
(…)(省略步骤,个人调试过程中请注意。)
继续调试,直到你进入到ExpressionEvaluator.java
2
3
4
5
6
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
[/code]
expression的值为
parameterObject的值为
以上两个参数之间好像有点关系,但离源泉处还差那么几步,请继续。
紧接着,我们进入到OgnlCache.java
2
3
4
5
6
7
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
[/code]
进入到OgnlCache.java
2
3
4
5
6
7
8
9
10
11
12
13
14
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]
key2:
1.
2. root的类型为
(…)(省略步骤,个人调试过程中请注意。)
当再继续执行的话,就回到了DefaultSqlSession.java
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
此时错误已经抛出了,见下图
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/26/d5661d207eb017c39324d856fa196bf2)
到了这,异常是找到怎么抛出了,但整体看上来,好像又缺点什么,没错,由于eclipse中无法再看到
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
此时再结合key2给出的内容,我们可以知道,要在
该不该搁下重重的壳,寻找哪里到底有蓝天──周杰伦《蜗牛》
本文出自:【沉默王二的博客】
(function () {('pre.prettyprint code').each(function () {
var lines = (this).text().split(′\n′).length;varnumbering = $('').addClass('pre-numbering').hide();
(this).addClass(′has−numbering′).parent().append(numbering);
for (i = 1; i
There is no getter for property named '*' in 'class Java.lang.String',此错误之所以出现,是因为mybatis在对
parameterType="String"的sql语句做了限制,假如你使用
<when test="username != null">这样的条件判断时,就会出现该错误,不过今天我们来刨根问底一下。
一、错误再现
想要追本溯源,就需要错误再现,那么假设我们有这样一个sql查询:< 4000 span class="hljs-tag"><select id="getRiskMember" resultMap="BaseResultMap" parameterType="String"> <include refid="selectMember"/> <choose> <when test="username != null"> and username = #{username} </when> <otherwise> and safetylevel > 1 </otherwise> </choose> </select>1
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
parameterType="String",这一点是必须得,参数类型必须是string。
该sql对应的mapper class中对应的方法为
List<Member> getRiskMember(String username);,也就是说,传递的参数名为username,正常情况下,这样的配置合情合理。
<when test="username != null">,你有一个对应的test判断语句,也可能是if。
那么这个时候,项目运行该查询语句时,就会抛出
There is no getter for property named 'username' in 'class java.lang.String'错误!
二、解决办法
当然了,如果你没有时间来看源码分析实例的话,我想先告诉你解决办法,免得你被问题困扰。解决办法很简单,你只需要把<when test="username != null">修改为
<when test="_parameter!= null">就好了,其他地方不需要改动(也就是说
and username = #{username}不需要改动为
and username = #{_parameter}),修改后的sql语句如下:
<select id="getRiskMember" resultMap="BaseResultMap" parameterType="String"> <include refid="selectMember"/> <choose> <when test="_parameter != null"> and username = #{username} </when> <otherwise> and safetylevel > 1 </otherwise> </choose> </select>1
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
三、源码分析
当然了,如果你有时间的话,看一看源码分析,或者自己动手尝试一下,我相信你一定会大有所获!①、准备源码包
你需要这样两个文件,具体怎么下载我就不多说了,如果你需要的话,也可以加群120926808:mybatis-3.2.3-sources.jar
mybatis-spring-1.2.2-sources.jar
当然了,你项目中对应的lib包也是相应的版本。
然后,我们把对应的源码进行反编译,生成对应的source,使用的工具是jd-gui.exe。
紧接着,我们来看看如何关联源码包,见下图:
我已经加载好了,如果是首次的话,可点击edit,在弹出的提示框中选择上一步保存的zip文件。
②、测试用例
准备好源码包后,我们来写一个测试用例,直接main方法就可以,当然了项目不同,方法自然不同,简单的如下所示:public static void main(String[] args) throws IOException { SpringUtils.getSpringContext(); MemberMapper mapper = SpringUtils.getBeansByClassType(MemberMapper.class); mapper.getRiskMember("00010001"); }1
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
我们在
mapper.getRiskMember("00010001");这行打上断点。
③、debug调试
直接运行main方法,在断点处F5,进入到MapperProxy.javapublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }1
2
3
4
5
6
7
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
[/code]
可以尾随debug进入到MapperMethod.java
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); 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; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[/code]
进入到该方法后,可以一直调试到
result = sqlSession.<E>selectList(command.getName(), param);该行代码。此时,你需要按住ctrl键,同时点击鼠标左键,见下图:
在弹出框中选择open implementation,然后进入到DefaultSqlSession.java
public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }1
2
3
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
[/code]
在
return this.selectList行上打上断点,然后按F8快捷键进入到该方法继续调试,(限于篇幅,省略步骤,后续文章中使用…代替)、直到你进入到CachingExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }1
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
tips:猫腻就在
BoundSql boundSql = ms.getBoundSql(parameterObject);这行代码的执行过程中。
(…)(省略步骤,个人调试过程中请注意。)
直到你进入到DynamicContext.java类时
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()); }1
2
3
4
5
6
7
8
9
10
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
[/code]
此时,你不妨wait a moment,翻看一下该类的整体代码,你会发现:
public static final String PARAMETER_OBJECT_KEY = "_parameter"; public static final String DATABASE_ID_KEY = "_databaseId";1
2
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
[/code]
这里有两个常量,当然了,但看此处,也许你会发现
"_parameter"这个关键字,但这时还说明不了什么,你且记住
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);,同时对
ContextMap bindings对象留有一点印象。
key1:_parameter
(…)(省略步骤,个人调试过程中请注意。)
然后,我们进入MixedSqlNode.java
public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; }1
2
3
4
5
6
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
[/code]
该apply方法就非常有意思了,xml里配置的sql语句,会通过该方法转换为标准的sql(称之为标准,是值这形成的sql语句就是能够执行预处理sql查询的字符串),你不妨慢一点执行该循环语句。
第二次循环的时候,你就可以看到sql的雏形了,那么请继续。
(…)(省略步骤,个人调试过程中请注意。)
直到你发现,sqlNode的类型为ChooseSqlNode,此时,你是否已经能联想到以下内容:
<choose> <when test="_parameter != null">1
2
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
[/code]
事情开始变得明朗起来,真好。
(…)(省略步骤,个人调试过程中请注意。)
继续调试,直到你进入到ExpressionEvaluator.java
public boolean evaluateBoolean(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value instanceof Boolean) return (Boolean) value; if (value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO); return value != null; }1
2
3
4
5
6
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
[/code]
expression的值为
username != null
parameterObject的值为
{_parameter=00010001, _databaseId=null}
以上两个参数之间好像有点关系,但离源泉处还差那么几步,请继续。
紧接着,我们进入到OgnlCache.java
public static Object getValue(String expression, Object root) { try { return Ognl.getValue(parseExpression(expression), root); } catch (OgnlException e) { throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e); } }1
2
3
4
5
6
7
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
[/code]
进入到OgnlCache.java
private static Object parseExpression(String expression) throws OgnlException { try { Node node = expressionCache.get(expression); if (node == null) { node = new OgnlParser(new StringReader(expression)).topLevelExpression(); expressionCache.put(expression, node); } return node; } catch (ParseException e) { throw new ExpressionSyntaxException(expression, e); } catch (TokenMgrError e) { throw new ExpressionSyntaxException(expression, e); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]
key2:
1.
parseExpression(expression)的类型为Node,其值为
username != null。
2. root的类型为
DynamicContext$ContextMap (id=41),其值为
{_parameter=00010001, _databaseId=null}
(…)(省略步骤,个人调试过程中请注意。)
当再继续执行的话,就回到了DefaultSqlSession.java
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }1
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
此时错误已经抛出了,见下图
到了这,异常是找到怎么抛出了,但整体看上来,好像又缺点什么,没错,由于eclipse中无法再看到
Ognl.getValue(parseExpression(expression), root);,所以就会造成困扰,我们通过反编译工具,可以看到getValue方法。
public static Object getValue(Object tree, Object root) throws OgnlException { return getValue(tree, root, null); }1
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
public static Object getValue(Object tree, Map context, Object root) throws OgnlException { return getValue(tree, context, root, null); }1
2
3
4
5
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
[/code]
public static Object getValue(Object tree, Map context, Object root, Class resultType) throws OgnlException { OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context); Object result = ((Node)tree).getValue(ognlContext, root); if (resultType != null) { result = getTypeConverter(context).convertValue(context, root, null, null, result, resultType); } return result; }1
2
3
4
5
6
7
8
9
10
11
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/9cc493f1e15b23d0f4eaea0d0f8b35d0.png)
1
2
3
4
5
6
7
8
9
10
11
[/code]
此时再结合key2给出的内容,我们可以知道,要在
{_parameter=00010001, _databaseId=null}匹配到porperty为
username的值是不可能的啦,这样的话,程序就会抛出
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'username' in 'class java.lang.String'错误了!
该不该搁下重重的壳,寻找哪里到底有蓝天──周杰伦《蜗牛》
本文出自:【沉默王二的博客】
(function () {('pre.prettyprint code').each(function () {
var lines = (this).text().split(′\n′).length;varnumbering = $('').addClass('pre-numbering').hide();
(this).addClass(′has−numbering′).parent().append(numbering);
for (i = 1; i
相关文章推荐
- 源码分析 There is no getter for property named '*' in 'class java.lang.String
- 源码分析 There is no getter for property named '*' in 'class java.lang.String
- 源码分析 There is no getter for property named '*' in 'class java.lang.String
- 源码分析 There is no getter for property named '*' in 'class java.lang.String
- 【Mybatis】There is no getter for property named 'type' in 'class java.lang.String'
- mybatis There is no getter for property named 'xx' in 'class java.lang.String
- mybatis加Spring项目: 解决There is no getter for property named '***' in 'class java.lang.String'问题
- mybatis There is no getter for property named 'xx' in 'class java.lang.String
- mybatis使用动态sql时报错:There is no getter for property named '*' in 'class java.lang.String
- mybaits错误解决:There is no getter for property named 'id' in class 'java.lang.String'
- mybatis There is no getter for property named 'xx' in 'class java.lang.String
- mybatis There is no getter for property named 'xx' in 'class java.lang.String
- Mybatis-There is no getter for property named 'XXX' in 'class java.lang.String'解决办法
- Mybatis-There is no getter for property named 'id' in 'class java.lang.String'
- MyBatis There is no getter for property named 'xxx' in 'class java.lang.String'
- 错误解决:There is no getter for property named 'id' in class 'java.lang.String'
- mybatis:There is no getter for property named 'xxx' in 'class java.lang.String'
- There is no getter for property named '**' in 'class java.lang.String'
- There is no getter for property named 'userIds' in 'class java.lang.String'
- MyBatis There is no getter for property named 'xxx' in 'class java.lang.String'