解决Seata代理数据源后MybatisPlus的增强组件全

一、现象描述

分布式事务选型Seata框架,在配置使用Seata的DataSourceProxy代理数据源后,发现之前配置的MyBatisPlus拦截器(分页、乐观锁、数据权限)失效了。

二、解决此问题的过程

存在问题的配置

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * Seata代理数据源配置
 *
 * @author YeMingXiang
 * @date 2021/08/26
 */
@Configuration
public class SeataDataSourceProxyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception {
        // 服务中引入了mybatis-plus,所以要使用特殊的SqlSessionFactoryBean
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        // 代理数据源
        sqlSessionFactoryBean.setDataSource(new DataSourceProxy(dataSource));
        // 生成SqlSessionFactory
        return sqlSessionFactoryBean.getObject();
    }
}
复制代码

1、尝试解决:为MybatisSqlSessionFactoryBean添加插件

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * Seata代理数据源配置
 *
 * @author YeMingXiang
 * @date 2021/08/26
 */
@Configuration
public class SeataDataSourceProxyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource, MybatisPlusInterceptor mybatisPlusInterceptor)
        throws Exception {
        // 服务中引入了mybatis-plus,所以要使用特殊的SqlSessionFactoryBean
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        // 为MybatisSqlSessionFactoryBean添加插件
        sqlSessionFactoryBean.setPlugins(mybatisPlusInterceptor);
        // 代理数据源
        sqlSessionFactoryBean.setDataSource(new DataSourceProxy(dataSource));
        // 生成SqlSessionFactory
        return sqlSessionFactoryBean.getObject();
    }

}
复制代码

启动项目进行测试,发现MyBatisPlus的分页功能恢复正常了(^-^)V

但是MyBatisPlus的乐观锁功能却报错了!

2、换种方式解决:

查看MybatisPlusAutoConfiguration源码,如下:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
    MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    applyConfiguration(factory);
    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 (this.properties.getTypeAliasesSuperType() != null) {
        factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
        factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        factory.setTypeHandlers(this.typeHandlers);
    }
    Resource[] mapperLocations = this.properties.resolveMapperLocations();
    if (!ObjectUtils.isEmpty(mapperLocations)) {
        factory.setMapperLocations(mapperLocations);
    }
    // TODO 修改源码支持定义 TransactionFactory
    this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);

    // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (!ObjectUtils.isEmpty(this.languageDrivers)) {
        factory.setScriptingLanguageDrivers(this.languageDrivers);
    }
    Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);

    // TODO 自定义枚举包
    if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
        factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
    }
    // TODO 此处必为非 NULL
    GlobalConfig globalConfig = this.properties.getGlobalConfig();
    // TODO 注入填充器
    this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
    // TODO 注入主键生成器
    this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i));
    // TODO 注入sql注入器
    this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
    // TODO 注入ID生成器
    this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
    // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
    factory.setGlobalConfig(globalConfig);
    return factory.getObject();
}
复制代码

发现MyBatisPlus的除了处理拦截器,还做了很多事情(未来还可能增加),所以我们最好还是使用其默认功能。那我们该怎么办呢?

看到MyBatisPlus的自动配置类里的sqlSessionFactory方法了吗,它也是一样的注入一个数据源,这时候大家应该都知道解决方法了吧?

就是把被代理过的数据源给放到MyBatisPlus的sqlSessionFactory中。

修改后的代码如下:

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * Seata代理数据源配置
 *
 * @author YeMingXiang
 * @date 2021/08/26
 */
@Configuration
public class SeataDataSourceProxyConfig {

    @Bean
    public DruidDataSource druidDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Primary
    @Bean
    public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }

}
复制代码

再次测试,已经解决了 O(∩_∩)O哈哈~

三、要注意的事项

1、DruidDataSource的创建方式

必须使用下面这种方式创建,而不能直接new DruidDataSource(),否则将导致读取不到配置文件中的Druid属性,以下方式实际返回的是DruidDataSourceWrapper,大家可以去看看它的源码。

@Bean
public DruidDataSource druidDataSource() {
    return DruidDataSourceBuilder.create().build();
}
复制代码

2、返回被代理过的datasource Bean,记得加上@Primary

参考文章:《解决seata代理数据源后mybatisplus的增强组件全部失效的bug》