【mybatis】 Invalid bound statement (not found): com.xxx.mapper.xxxMapper.selectByxx

背景:

业务功能开发,新增一些查询功能,对应地创建了一个 mapper 接口类,一个 mapper.xml 文件。

这个mapper 的命名是: Rolemapper.xml (盯着它)

什么namespace, 方法名都没有名,
但是在执行方法查询的时候,却冒出了一个错误:

Invalid bound statement (not found): com.xxx.mapper.xxxmapper.selectByxx

多次检查,确实没发现什么问题,
网上找的一些解决方案,都检查了一次。
连idea 都清cache重启了一次,但还是不行。

看现象,就是这个 mapper 没有被扫描到。

怎么会出现这样的情况呢?

问题定位过程

那些百度上找的答案,一一核对过,没发现问题。
然后,把这个查询方法,写到另外一个原有的 mapper 上面, sql也添加到对应的 mapper.xml 文件上。—— 可以调用。

那基本上可以确定,就是新增的这个 mapper 的原因了。但就这么简单的一个 mapper 接口类,怎么看怎么没有问题,那问题可以出现在哪里呢?

为什么之前的 mapper 接口类可以,新增的 mapper 类不行呢?

问题发现

其实刚才已经知道就是新增的 mapper 没有扫描到而引起的原因,再看一下,为什么没有扫描到呢?
确实找了好一会儿,同事说,你这个 mapper 接口类,名字的这个 "mapper" 应该改为 "Mapper" —— 恍然大悟。emmm,,, 有够低级的。

知道了答案再找原因,就很容易了,看一下,我们的Mybatis 扫描配置, 是这样写的:

mybatis:
  mapper-locations:
    classpath*:sqlmap/*/*Mapper.xml,classpath*:sqlmap/*Mapper.xml   
   (请看这里,它只会扫描  *Mapper.xml !!!!  小写的  *mapper.xml 它就不认了!!!!!!!)

我特么。。。。害。
定位问题的方向,一开始就没往这里找,。。。emmmm。

行吧。记录一下。可能也有小伙伴会出现类似的问题,不一小心就很容易出错,虽然低级,但容易出错。


记录另外一种可能性:

有可能是 idea 的问题。
尝试过,idea 启动是报错的,
但代码 stash 一下,然后启动,启动成功(这个时候以为是新修改的代码引发的问题)
启动成功后,再把新代码覆盖回来,发现错误重现不了了。
(感觉又不是代码的问题,是某一次编译错误后,它就好不了了。得再成功编译一下,它才能刷新得了)




记录于2023年

再记录一次一模一样的问题

是的,又一次翻车,一模一样的报错,熟悉的恐惧感。
产生了一种我不是干这一行的感觉,被配置信息支配的恐惧。

来,说重点:
报错现象是一模一样的,都是 Invalid bound statement (not found): com.xxx.mapper.xxxmapper.selectByxx
但是,该写的 mapper 接口,mapper.xml 都没有错。果断没有错。mapper 类也被 spring 管理到了,但是呢,这个类在调用方法的时候,就是找不到它对应的 mapper。
类路径什么的都没有错,
但它就是没有加载上来(其实当时应该往这个为什么没有加载 mapper.xml 方法上再多想一下,实质上并没有。。)

这就是背景了。

嗯,然后呢,我放弃了。是的我放弃了。
等一下,这里面其实是有一个问题的,代码,我是“抄”过来的,抄过来代码还能有错?
是的,事儿坏就坏在,我抄了代码,并且还抄了一份水土不服的代码。
怎么说呢,

如果正常来说,springboot+mybatis+druid 的一个框架,
只要把配置文件写好,启动类该扫描的包扫描到,加上正确的 mapper 类, mapper文件,最多加一个 druid 的配置解析类,就完事了。——理论上是这样子的。
嗯,这里其它的都没啥问题,
单来说一下这个 DruidConfiguration 类,嗯,是的。我们写了这个东西,
正常项目使用的 DruidConfiguration 大概是这样子的:

DruidConfiguration / druid配置类
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.ResourceServlet;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.sql.SQLException;

import javax.sql.DataSource;

@Configuration
@EnableConfigurationProperties(DruidProperties.class)
@ConditionalOnClass(DruidDataSource.class)
@ConditionalOnProperty(prefix = "druid", name = "url")
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class DruidAutoConfiguration {

    @Autowired
    private DruidProperties properties;
    /**
     * 获取数据源
     * @return DataSource
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(properties.getUrl());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());
        if (properties.getInitialSize() > 0) {
            dataSource.setInitialSize(properties.getInitialSize());
        }
        if (properties.getMaxActive() > 0) {
            dataSource.setMaxActive(properties.getMaxActive());
        }
        if (properties.getMaxWait() > 0) {
            dataSource.setMaxWait(properties.getMaxWait());
        }
        if (properties.getMinIdle() > 0) {
            dataSource.setMinIdle(properties.getMinIdle());
        }
        if (properties.getValidationQuery() != null) {
            dataSource.setValidationQuery(properties.getValidationQuery());
        }
        dataSource.setTestOnBorrow(properties.isTestOnBorrow());
        dataSource.setUseUnfairLock(properties.isUseUnfairLock());
        dataSource.setRemoveAbandoned(properties.isRemoveAbandoned());
        dataSource.setRemoveAbandonedTimeout(properties.getRemoveAbandonedTimeout());

        try {
            dataSource.setFilters(properties.getFilters());
            dataSource.init();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return dataSource;
    }

    /**
     * 获取ServletRegistrationBean
     * @return ServletRegistrationBean
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        StatViewServlet druidServlet = new StatViewServlet();
        ServletRegistrationBean druidServletRegistration = new ServletRegistrationBean(druidServlet);
        druidServletRegistration.addInitParameter("allow", "");
        druidServletRegistration.addUrlMappings("/druid/*");
        druidServletRegistration.addInitParameter(ResourceServlet.PARAM_NAME_USERNAME, "admin");
        druidServletRegistration.addInitParameter(ResourceServlet.PARAM_NAME_PASSWORD, "ydadmin");
        druidServletRegistration.addInitParameter("resetEnable", "false");
        return druidServletRegistration;
    }

    /**
     * @description 注册一个过滤器,允许页面正常浏览
     * @return filter registration bean
     */
    @Bean
    public FilterRegistrationBean druidStatFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(
                new WebStatFilter());
        // 添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");
        // 添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

嗯,其实如果真的用了这个,倒也没啥问题。问题是,我看的是另外一个:它是长这样子的:

// 省略一些 import

@Configuration
@EnableConfigurationProperties({DruidProperties.class})
@ConditionalOnClass({DruidProperties.class})
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@Slf4j
public class DruidAutoConfiguration2 {

    /**
     * 获取数据源  优先加载,默认数据库
     *
     * @return DataSource
     */
    @Primary
    @Bean(name = "mysqlDataSource")
    public DataSource dataSource(DruidProperties properties) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(properties.getUrl());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());
        // <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
        if (properties.getInitialSize() > 0) {
            dataSource.setInitialSize(properties.getInitialSize());
        }
        if (properties.getMaxActive() > 0) {
            dataSource.setMaxActive(properties.getMaxActive());
        }
        if (properties.getMaxWait() > 0) {
            dataSource.setMaxWait(properties.getMaxWait());
        }
        if (properties.getMinIdle() > 0) {
            dataSource.setMinIdle(properties.getMinIdle());
        }
        if (properties.getValidationQuery() != null) {
            dataSource.setValidationQuery(properties.getValidationQuery());
        }
        dataSource.setTestOnBorrow(properties.isTestOnBorrow());

        try {
            dataSource.init();
        } catch (SQLException e) {
            if (log.isErrorEnabled()) {
                log.error("数据库初始化异常...");
            }
            Thread.currentThread().interrupt();
        }

        return dataSource;
    }

    /**
     *
     *
     * @return ServletRegistrationBean
     */
    @Primary
    @Bean(name = "mySqlSessionFactory")
    public SqlSessionFactory mySqlSessionFactory(@Qualifier("myDataSource") DataSource dataSource, MybatisProperties properties) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 获取ServletRegistrationBean
     *
     * @return ServletRegistrationBean
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        StatViewServlet druidServlet = new StatViewServlet();
        ServletRegistrationBean druidServletRegistration = new ServletRegistrationBean(druidServlet);
        druidServletRegistration.addInitParameter("allow", "127.0.0.1");
        druidServletRegistration.addUrlMappings("/druid/*");
        return druidServletRegistration;
    }

    @Bean(name = "druidTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("myDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

嗯, 快看一下它有什么特别!
是的!它定义了 SqlSessionFactory 这个bean, 然后呢,看一下这个:MybatisAutoConfiguration.java

package org.mybatis.spring.boot.autoconfigure;

// 省略...

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {

// 省略...

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }

        factory.setConfiguration(this.properties.getConfiguration());
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }

        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }

        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }

        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }

        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }

        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }

        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

// 省略...

}

MybatisAutoConfiguration 这个类里面,定义了一个 SqlSessionFactory sqlSessionFactory(DataSource dataSource) 方法,并且它是 @ConditionalOnMissingBean 的。如果自己定义了 SqlSessionFactory, 就不会走这个。
但如果自己写错了的话,那就凉凉了。很明显,我是后者。

抄作业,都挑了一个错误的作业来操。
对自己表示极度无语。

看这个类,人家是有 actory.setMapperLocations(this.properties.resolveMapperLocations()); 这一行的。但。。。emmmm

知道原因就好些了。
于是在原来的位置,加上这一行就好了。
比如这样:

    public SqlSessionFactory mySqlSessionFactory(@Qualifier("myDataSource") DataSource dataSource, MybatisProperties properties) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(properties.resolveMapperLocations());
        return sqlSessionFactoryBean.getObject();
    }

当然,还有另外一种更方便的方案,就是不要自已去定义这个。

emmmm,
好吧,又浪费了一两天。因为对mybatis源码不够了解,本来很简单的事情,搞得很麻烦。

记录一下。

posted @ 2022-12-27 11:12  aaacarrot  阅读(227)  评论(0编辑  收藏  举报