1. 程式人生 > >springboot整合mybatis——報錯There is no getter for property named '*' in 'class java.lang.String

springboot整合mybatis——報錯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在對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
  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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

三、原始碼分析

當然了,如果你有時間的話,看一看原始碼分析,或者自己動手嘗試一下,我相信你一定會大有所獲!

①、準備原始碼包

你需要這樣兩個檔案,具體怎麼下載我就不多說了,如果你需要的話,也可以加群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");
}
  • 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
  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);
    }
  }
  • 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: 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

此時錯誤已經丟擲了,見下圖

這裡寫圖片描述

到了這,異常是找到怎麼丟擲了,但整體看上來,好像又缺點什麼,沒錯,由於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'錯誤了!

該不該擱下重重的殼,尋找哪裡到底有藍天──周杰倫《蝸牛》 本文出自:【沉默王二的部落格

閱讀更多