關於mybatis的org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):

前言

今天遇到一個很鬱悶的錯誤:org.apache.ibatis.binding.BindingException: Invalid bound
statement (not found)。 奇怪的是我dao接口與mybatis映射文件配置都是正確的,但還是報錯

正文

下面,我通過源碼去探索一番。這次的探索之旅離不開debug,而項目是maven,所以在探索之前,必須得知道maven如何debug,具體文章在

please click
準備工作差不多了,開始重現錯誤,報錯入下:
這裏寫圖片描述
定位214行

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      String statementName = mapperInterface.getName() + "." + method.getName();
      MappedStatement ms = null;
      if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        if(method.getAnnotation(Flush.class) != null){
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): " + statementName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

從上面代碼看兩種途徑binding,或者是配置文件,或者是註解。這裏配置文件,問題出在configuration.hasStatement(statementName)爲false. 進去看一下爲什麼會false

入下代碼

public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {
    if (validateIncompleteStatements) {
      buildAllStatements();
    }
    return mappedStatements.containsKey(statementName);
  }

這裏是發生本次異常比較關鍵的地方了,爲什麼我會說關鍵? 因爲我從代碼mappedStatements.containsKey(statementName); 中,可以看出2個會發生該異常的情況

  1. mappedStatements爲{} (空,debug發現,我這裏爲空)
  2. containsKey(statementName),沒有找到

mappedStatements爲{}

先看第一點。 mappedStatements爲什麼是空的呢? 它是在什麼時候初始化的呢。
這裏寫圖片描述

在tomcat啓動的時候,進入spring監聽函數,裏面將去解析spring配置文件,將去加載紅框內的資源文件,解析並以key,value的形式緩存在mappedStatements中
具體緩存代碼,在下面簡單的羅列一下

XmlMapperBuilder方法的parse

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

進入configurationElement方法,在選擇其方法下的

buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 

類層次關係挺複雜的,有閒工夫就花類圖理一下,最後找到下面MapperBuilderAssistant類中的addMappedStatement方法

MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);

解析完成了,我這次異常罪魁禍首,猜測是上面的紅框內路徑有問題,所以沒緩存mappedStatements中(其實路徑之前修改幾次,最後是對的,只不過因爲我eclipse取消自動編譯,沒編譯,淚奔~)

在看源碼的時候,我發現spring,將接口dao,動態代理了一個類,然後存入了IOC,所以我在service層,可以直接使用@Resource注入,至於如何將dao類存放到IOC中,不需要關心

從mappedStatements獲取

上面講過了將mybatis的映射文件緩存在mappedStatements中,具體key爲ms.getId().

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

這裏的id規則是,mybatis映射文件的namespace + id,如下

<mapper namespace="org.personal.perfect.mpersistent.dao.TestMybatis">
    <resultMap type="map" id="myinfo"></resultMap>
    <select id="selectInformation" parameterType="map" resultType="int">
        select count(1) from information
    </select> 
    <select id="selectInformationfor2" parameterType="map" resultType="map">
        select * from information
    </select>

    <select id="selectInformationfor3" parameterType="map" resultType="map">
        select * from information where id = #{id}
    </select> 
    <select id="selectInformationfor4" parameterType="map" resultType="Information">
        select * from information where id = #{id}
    </select> 
</mapper>

而mappedStatements.containsKey(statementName)中的statementName獲取的key規則是什麼呢

String statementName = mapperInterface.getName() + "." + method.getName();

也就是通過反射,獲取類全路徑限定名以及方法名,所以這也說明了接口名= namespace 方法名 = id. 趕緊檢查一下吧

總結

1.spring與mybatis繼承,配置自動掃描的話,趕緊看看資源路徑是否對吧
2. dao接口名 = namespace ; 接口方法名 = id.
3. 千萬要編譯的(3+2 hours,最後突然可行了,淚奔啊)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章