spring boot mybatis 報錯Invalid bound statement (not found)解決過程


  通過解決Invalid bound statement (not found),剖析mybatis加載Mapper接口、Mapper.xml以及將兩者綁定的過程。

  項目剛開始使用了spring boot mybatis:

  1.配置掃描mapper接口

@MapperScan({"com.hbfec.encrypt.mbg.mapper","com.hbfec.encrypt.admin.dao"}) 


  2.在application.yml中配置Mapper.xml的掃描路徑

mybatis:
mapper-locations:
- classpath:dao/**/*.xml
- classpath*:com/**/mapper/*.xml

  一切接口正常訪問。
  因為需要使用雙數據源,自定義了DataSource SqlSessionFactory SqlSessionTemplate的bean,不再使用MybatisAutoConfiguration.class中默認的Bean

 @Resource(name = "dsTwo")
    DataSource dsTwo;//DataSource 也為自定義
    @Bean("sqlSessionFactory2")
    @Qualifier("sqlSessionFactory2")
    SqlSessionFactory sqlSessionFactory2() {
        SqlSessionFactory sessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dsTwo);
            sessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    @Bean("sqlSessionTemplate2")
    @Qualifier("sqlSessionTemplate2")
    SqlSessionTemplate sqlSessionTemplate2() {
        return new SqlSessionTemplate(sqlSessionFactory2());
    }

  

項目啟動后調用Mapper接口,部分接口正常訪問,部分接口比如:TestDao.findAll,訪問報一下錯誤:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.hbfec.encrypt.admin.dao.ocr.TestDao.findAll
    at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:227)
    at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:49)
    at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:65)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58)
    at com.sun.proxy.$Proxy118.findAll(Unknown Source)
    at com.hbfec.encrypt.admin.controller.TestController.findAll(TestController.java:24)

  在網上試了各種手段都無法解決,只能去源碼DEBUG了。

  1.根據第一行錯誤信息:at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:227) 定位到MapperMethod的SqlCommand方法

 

  發現是 MappedStatement ms == null的情況下拋出的異常,那么MappedStatement是什么呢?

  MappedStatement對象對應Mapper.xml配置文件中的一個select/update/insert/delete節點,描述的就是一條SQL語句,所以可以猜測為對應的Mapper.xml文件沒有找到。

  ms對象從resolveMappedStatement方法中解析

MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);

  繼續觀察resolveMappedStatement為什么會返回null值?

  

 

 

 

 

 

 

    發現configuration 對象(全局配置對象)里面的mappedStatements為空,沒有加載到任何的Mapper,以下是Configuration類中定義的mappedStatements

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

    

    找到這里就知道為什么會出現如上錯誤,TestDao接口對應的xml文件沒有被正確加載或者沒有找到接口對應的xml文件。

  2.為什么mappedStatements值為空呢?configuration 對象什么時候進行初始化mappedStatements的?

   我們知道一個MappedStatement對象對應一個mapper.xml中的一個SQL節點,而Mapper.xml文件是初始化Configuration對象的時候進行解析加載的,則說明MappedStatement對象就是在初始化Configuration對象的時候創建的。

   所以找到初Configuration對象初始化MappedStatement的地方進行DEBUG;

  Configuration對象中添加Mapper使用MapperRegistry類

public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
}

 

  繼續往下

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse();        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

 

   紅色部分為解析xml並初始化MappedStatement對象的代碼。繼續看看MapperAnnotationBuilder類的parse()方法:

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

 

  紅色部分為加載Xml資源,繼續看

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

 

  在這里尋找mapper接口對應的xml的資源路徑的方式如下:
    

String xmlResource = type.getName().replace('.', '/') + ".xml";

  替換接口包名中的.為/ 並在接口添加.xml后綴。
  比如:Mapper接口com.hbfec.encrypt.admin.dao.ocr.TestDao的對應的xml資源路徑會解析為com/hbfec/encrypt/admin/dao/ocr/TestDao.xml。
  TestDao.xml在我的項目中的路徑是classpath:dao/ocr/TestDao.xml,路徑與上面解析出來的不一致,mybatis無法找到TestDao.xml,導致以上錯誤。所以項目采用使用這種方式綁定Mapper接口和Mapper.xml的話,其路徑和名稱都要一致
  解決辦法有以下兩種:

  1.在resource下創建和TestDao接口所在目錄一樣的包路徑,使TestDao.xml和TestDao接口的包路徑一致。


  2.在自定義的SqlSessionFactory中添加 bean.setMapperLocations(mybatisProperties.resolveMapperLocations()),使yml中的配置信息mapper-locations信息生效。

改造自定義的SqlSessionFactory如下:

@Configuration
@ConditionalOnClass(SqlSessionFactoryBean.class)
@MapperScan(basePackages = "com.hbfec.encrypt.admin.dao.ocr",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigOcr {
    @Resource(name = "dsTwo")
    DataSource dsTwo;
  @Autowired MybatisProperties mybatisProperties;

    @Bean("sqlSessionFactory2")
    @Qualifier("sqlSessionFactory2")
    SqlSessionFactory sqlSessionFactory2() {
        SqlSessionFactory sessionFactory = null;
        try {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dsTwo);
          bean.setMapperLocations(mybatisProperties.resolveMapperLocations());//將mapper-locations的配置信息注入
            sessionFactory = bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    @Bean("sqlSessionTemplate2")
    @Qualifier("sqlSessionTemplate2")
    SqlSessionTemplate sqlSessionTemplate2() {
        return new SqlSessionTemplate(sqlSessionFactory2());
    }
}

 

問題1:為什么單數據源的時候classpath:dao 下面的Mapper.xml可以被正確加載

答:自定義的SqlSessionFactory導致MybatisAutoConfiguration中的SqlSessionFactory bean失效,該bean中使用了MybatisProperties從配置文件application.yml中加載的mybatis配置信息,比如mapperLocations ,也同時失效。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM