一、现象描述
分布式事务选型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();
}
复制代码
近期评论