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

简介: 版权声明:欢迎转载,请注明沉默王二原创。 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. parameterType="String",这一点是必须得,参数类型必须是string。
  2. 该sql对应的mapper class中对应的方法为List<Member> getRiskMember(String username);,也就是说,传递的参数名为username,正常情况下,这样的配置合情合理。
  3. <when test="username != null">,你有一个对应的test判断语句,也可能是if。
  4. 那么这个时候,项目运行该查询语句时,就会抛出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:

  1. mybatis-3.2.3-sources.jar
  2. 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);这行代码的执行过程中。

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

直到你进入到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());
  }

此时,你不妨wait a moment,翻看一下该类的整体代码,你会发现:

  public static final String PARAMETER_OBJECT_KEY = "_parameter";
  public static final String DATABASE_ID_KEY = "_databaseId";

这里有两个常量,当然了,但看此处,也许你会发现"_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;
  }

该apply方法就非常有意思了,xml里配置的sql语句,会通过该方法转换为标准的sql(称之为标准,是值这形成的sql语句就是能够执行预处理sql查询的字符串),你不妨慢一点执行该循环语句。

这里写图片描述

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

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

这里写图片描述

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

<choose>
        <when test="_parameter != null">

事情开始变得明朗起来,真好。

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

继续调试,直到你进入到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. expression的值为username != null
  2. parameterObject的值为{_parameter=00010001, _databaseId=null}
  3. 以上两个参数之间好像有点关系,但离源泉处还差那么几步,请继续。

紧接着,我们进入到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);
    }
  }

进入到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);
    }
  }

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();
    }
  }

此时错误已经抛出了,见下图

这里写图片描述

到了这,异常是找到怎么抛出了,但整体看上来,好像又缺点什么,没错,由于eclipse中无法再看到Ognl.getValue(parseExpression(expression), root);,所以就会造成困扰,我们通过反编译工具,可以看到getValue方法。

  public static Object getValue(Object tree, Object root)
    throws OgnlException
  {
    return getValue(tree, root, null);
  }
  public static Object getValue(Object tree, Map context, Object root)
    throws OgnlException
  {
    return getValue(tree, context, root, null);
  }
  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;
  }

此时再结合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'错误了!

该不该搁下重重的壳,寻找哪里到底有蓝天──周杰伦《蜗牛》
本文出自:【沉默王二的博客

相关文章
|
9天前
|
Java
java基础(4)public class 和class的区别及注意事项
本文讲解了Java中`public class`与`class`的区别和注意事项。一个Java源文件中只能有一个`public class`,并且`public class`的类名必须与文件名相同。此外,可以有多个非`public`类。每个类都可以包含一个`main`方法,作为程序的入口点。文章还强调了编译Java文件生成`.class`文件的过程,以及如何使用`java`命令运行编译后的类。
15 3
java基础(4)public class 和class的区别及注意事项
|
9天前
|
Java
java的class类
java的class类
18 5
|
2月前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
2月前
|
Java 测试技术 Docker
记录一次很坑的报错:java.lang.Exception: The class is not public.
这篇文章记录了作者在Docker中运行服务进行单元测试时遇到的一系列问题,包括Spring Boot与Spring Cloud版本不一致、Bean注入问题、测试单元引入问题以及公共类和方法的可见性问题,并提供了解决问题的方法和成功测试通过的代码示例。
记录一次很坑的报错:java.lang.Exception: The class is not public.
|
2月前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
2月前
|
Java
JAVA中public class和class的区别
JAVA中public class和class的区别
34 7
|
2月前
|
Oracle Java 关系型数据库
简单记录在Linux上安装JDK环境的步骤,以及解决运行Java程序时出现Error Could not find or load main class XXX问题
本文记录了在Linux系统上安装JDK环境的步骤,并提供了解决运行Java程序时出现的"Error Could not find or load main class XXX"问题的方案,主要是通过重新配置和刷新JDK环境变量来解决。
74 0
|
4月前
|
存储 Java 测试技术
滚雪球学Java(66):Java之HashMap详解:深入剖析其底层实现与源码分析
【6月更文挑战第20天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
40 3
滚雪球学Java(66):Java之HashMap详解:深入剖析其底层实现与源码分析
|
3月前
|
缓存 监控 Java
(十)深入理解Java并发编程之线程池、工作原理、复用原理及源码分析
深入理解Java并发编程之线程池、工作原理、复用原理及源码分析
|
3月前
|
Java
Error:Internal error: (java.lang.IllegalAccessError) class com.,idea2019.3版本,必须用application2.7.6或者以下
Error:Internal error: (java.lang.IllegalAccessError) class com.,idea2019.3版本,必须用application2.7.6或者以下
下一篇
无影云桌面