zoukankan      html  css  js  c++  java
  • 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 ,也同时失效。

  • 相关阅读:
    docker in docker
    docker社区的geodata/gdal镜像dockerfile分析
    howto:在构建基于debian的docker基础镜像时,更换国内包源
    使用Visual Studio 2017构建.Net Core的Docker镜像
    步骤:asp.net core中使用identifyserver4颁发令牌
    部署:阿里云ECS部署Docker CE
    问题:调用 ASP.Net Core WebAPI的HTTP POST方法时,从 [FromBody] 中读取的 MongoDB GeoJsonObjectModel成员总是null
    《.NET 微服务:适用于容器化 .NET 应用的体系结构》关键结论
    SQL数据库注入防范 ASP.NET Globle警告
    数据库中的恶意字符批处理
  • 原文地址:https://www.cnblogs.com/hopeofthevillage/p/12858565.html
Copyright © 2011-2022 走看看