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>
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>
###三、源码分析
当然了,如果你有时间的话,看一看源码分析,或者自己动手尝试一下,我相信你一定会大有所获!
####①、准备源码包
你需要这样两个文件,具体怎么下载我就不多说了,如果你需要的话,也可以加群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"); }
我们在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); }
可以尾随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; }
进入到该方法后,可以一直调试到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); }
在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); }
tips:猫腻就在BoundSql boundSql = ms.getBoundSql(parameterObject);这行代码的执行过程中。
(…)(省略步骤,个人调试过程中请注意。)