多数据源环境下的 MyBatis Mapper 冲突问题排查与解决(MySQL与PgSQL)

133 阅读3分钟

问题背景

在已有Mysql项目中,其中一些库需要迁移到Pgsql中,领导说用PgSql性能高一点。(做复杂查询的Text类型数据)

在项目中同时使用 MySQL 和 PostgreSQL 两种数据库,要求:

  • 默认Mapper为 MySQL,主要处理绝大部分业务逻辑。且为Mybatis-Plus
  • 特定的Mapper使用 PostgreSQL,并配套独立的 MyBatis Mapper。

遇到的问题

  1. 起初设想,能否MySQL用Nacos的配置文件,单独给PgSQL配置Java数据源

结果会报错,NoSuchBeanDefinitionException,原因可能是我需要配置PgSQL的Mapper会和正常的Mapper放在一个文件夹,新配置的PgSQL数据源将其覆盖了

  1. 分别配置两个Java的SQL数据源,调用正常的Mapper的方法仍然会报错
    • No qualifying bean of type 'org.mybatis.spring.SqlSessionTemplate' available: expected single matching bean but found 2
    • 本身该方法返回的就是列表值,现在反而限制返回

解决

问题一解决:

将需要配置的Mapper移动到对应文件夹,并增加MySQL和PgSQL的单独Java配置

image.png

MySQL数据源代码:

@Configuration
@MapperScan(
    basePackages = "com.opencloud.qywechat.server.mapper", // 扫描整个包
    sqlSessionFactoryRef = "mysqlSqlSessionFactory" // 默认绑定 MySQL 数据源
)
public class MysqlDataSourceConfig {

    @Bean(name = "mysqlDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "mysqlSqlSessionFactory")
    @Primary
    public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/mysql/**/*.xml")
        );
        return sessionFactory.getObject();
    }

    @Bean(name = "mysqlSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate mysqlSqlSessionTemplate(
            @Qualifier("mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

PgSQL数据源代码

@Configuration
@MapperScan(
    basePackages = "com.opencloud.qywechat.server.mapper", // 扫描整个包
    annotationClass = PgSqlMapper.class, // 只扫描标注了 @PgSqlMapper 的 Mapper
    sqlSessionFactoryRef = "pgsqlSqlSessionFactory"
)
public class PgsqlDataSourceConfig {

    @Bean(name = "pgsqlDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.pgsql")
    public DataSource pgsqlDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "pgsqlSqlSessionFactory")
    public SqlSessionFactory pgsqlSqlSessionFactory(@Qualifier("pgsqlDataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/pgsql/**/*.xml")
        );
        return sessionFactory.getObject();
    }

    @Bean(name = "pgsqlSqlSessionTemplate")
    public SqlSessionTemplate pgsqlSqlSessionTemplate(
            @Qualifier("pgsqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

这里我自定义了注解@PgSqlMapper,检测到有该注解的Mapper才使用PgSQL的数据源

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface PgSqlMapper { 
}

Tips: 不同数据源的Mapper不要放在同一文件夹下

因为业务问题,对应的PgSQL的Mapper不得不放到MySQL的Mapper的子文件夹下面,所以,我在MySQL数据源配置加上@Primary配置,表明先加载这个数据源,后面用到对应PgSQL的Mapper再通过@PgSqlMapper使用PgSQL数据源。

问题二解决:

查了资料才发现

MyBatis-Plus 作为 MyBatis 的增强工具,其底层依赖与 MyBatis 类似,但在部分配置上存在差异。例如,SqlSessionFactory 需要用 MyBatis-Plus 提供的 MybatisSqlSessionFactoryBean 来替代原生的 SqlSessionFactoryBean,否则可能会导致某些增强功能(如分页插件)失效。

所以将其改为对应的 MybatisSqlSessionFactoryBean 即可

/**
 * 配置 MyBatis-Plus 的 SqlSessionFactory
 */
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
    MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);

    // 指定 Mapper XML 文件的位置
    sqlSessionFactoryBean.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml")
    );

    return sqlSessionFactoryBean.getObject();
}

同样的,如果PgSQL也用到MP相关的,也要配置MybatisSqlSessionFactoryBean加强。

除此之外,如果配置了MP的分页插件,也要在数据源的sqlSession工厂里添加上去

@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
    MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);

    // 配置分页插件
    sqlSessionFactoryBean.setPlugins(new PaginationInterceptor());

    sqlSessionFactoryBean.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/mysql/**/*.xml")
    );
    return sqlSessionFactoryBean.getObject();
}