当前位置 : 主页 > 编程语言 > 其它开发 >

源码分析 There is no getter for property named '*' in 'class java.lang.String

来源:互联网 收集:自由互联 发布时间:2022-07-12
原文链接:https://blog.csdn.net/qing_gee/article/details/47122227 There is no getter for property named '*' in 'class java.lang.String',此错误之所以出现,是因为mybatis在对parameterType="String"的sql语句做了限制,假

原文链接: https://blog.csdn.net/qing_gee/article/details/47122227

There is no getter for property named '*' in 'class java.lang.String',此错误之所以出现,是因为mybatis在对parameterType="String"的sql语句做了限制,假如你使用<when test="username != null">这样的条件判断时,就会出现该错误,不过今天我们来刨根问底一下。

###一、错误再现
想要追本溯源,就需要错误再现,那么假设我们有这样一个sql查询:

<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
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
###三、源码分析
当然了,如果你有时间的话,看一看源码分析,或者自己动手尝试一下,我相信你一定会大有所获!

####①、准备源码包
你需要这样两个文件,具体怎么下载我就不多说了,如果你需要的话,也可以加群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
我们在mapper.getRiskMember("00010001");这行打上断点。

####③、debug调试
直接运行main方法,在断点处F5,进入到MapperProxy.java

public 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
可以尾随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
进入到该方法后,可以一直调试到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
在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
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
此时,你不妨wait a moment,翻看一下该类的整体代码,你会发现:

public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
1
2
这里有两个常量,当然了,但看此处,也许你会发现"_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
该apply方法就非常有意思了,xml里配置的sql语句,会通过该方法转换为标准的sql(称之为标准,是值这形成的sql语句就是能够执行预处理sql查询的字符串),你不妨慢一点执行该循环语句。

 

第二次循环的时候,你就可以看到sql的雏形了,那么请继续。

(…)(省略步骤,个人调试过程中请注意。)

 

直到你发现,sqlNode的类型为ChooseSqlNode,此时,你是否已经能联想到以下内容:

<choose>
<when test="_parameter != null">
1
2
事情开始变得明朗起来,真好。

(…)(省略步骤,个人调试过程中请注意。)

继续调试,直到你进入到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
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
进入到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
key2:

parseExpression(expression)的类型为Node,其值为username != null。
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
此时错误已经抛出了,见下图

 

到了这,异常是找到怎么抛出了,但整体看上来,好像又缺点什么,没错,由于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
public static Object getValue(Object tree, Map context, Object root)
throws OgnlException
{
return getValue(tree, context, root, null);
}
1
2
3
4
5
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
此时再结合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'错误了!

最近,有不少读者问我是怎么学习的,那我干脆就把我看过的一些优质书籍贡献出来:
计算机基础入门推荐:《程序是怎样跑起来的》、《网络是怎样连接的》、《计算机是怎样跑起来的的》

进一步认识计算机网络:《计算机网络:自顶向下》、《图解http》

数据结构+算法入门:《大话数据结构》、《阿哈算法》

算法进阶:《算法第四版》、《编程珠玑》

由于我是 Java 技术栈的,顺便推荐几本 Java 的书籍,从左到由的顺序看到

Java:《Java核心技术卷1》、《编程思想》、《深入理解Java虚拟机》、《effective Java》、《Java并发编程的艺术》

数据库:《mysql必知必会》、《MySQL技术内幕:InnoDB存储引擎》

就先介绍这么多,这些都是最基础最核心的,希望对那些不知道看什么书的同学有所帮助。

对了,我介绍的这些书籍,已经顺便帮你整理好了,你可以在我的原创微信公众号『沉默王二』回复『书籍』获取哦

有收获?希望老铁们来个三连击,给更多的同学看到这篇文章
1、老铁们,关注我的原创微信公众号「沉默王二」,专注于有趣有益的程序人生,保证你看完有所收获,不信你打我。

2、给二哥点个赞呗,可以让更多的人看到这篇文章,顺便激励下我,嘻嘻。

SELECT
*
FROM
product_db.product p
WHERE
p.isvalid = 1
<if test="list != null">
<foreach collection="list" index="index" item="item" separator="," open="AND p.id IN (" close=")">
#{item}
</foreach>
</if>

网友评论