多数据源下@Transaction失效
如springboot项目中,手工创建具体数据源,通过@Primary实现多数据源并存。其中遇到两个问题
问题1:@Bean实现时,加上@ConfigurationProperties(prefix = "spring.datasource.datasource1")读取不到具体配置,失效。这个暂时未查到具体原因。
问题2:@Transaction注解不生效,原因是多数据源后,@Transaction触发org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction时,因为没有指定使用哪个事务管理器。源码:
final TransactionManager tm = determineTransactionManager(txAttr);
org.springframework.transaction.interceptor.TransactionAspectSupport#determineTransactionManager
/**
* Determine the specific transaction manager to use for the given transaction.
*/
@Nullable
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
// Do not attempt to lookup tx manager if no tx attributes are set
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(this.beanFactory, qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
}
else {
TransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
// 均没有指定事务管理器的时候,会从spring factory里拿,这时候会拿到多个,通过@primary返回了默认的主bean
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}
解决方式:只需@Transaction(value = "datasource1")就可以指定到具体的事物管理器,事务管理器中,是有datasource属性的,所以如果mybatis操作时用的是datasource2,但回滚时用了datasource1的事务管理器,就相当于没回滚。
详细的多数据源实现:
package com.shopline.paymentacceptance.merchantservice.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* @author : liangmingkun
* @date : 2022/11/21 09:47
* @Desc :
*/
@Configuration
public class DataSourceConfig {
@Bean(name = "merchantAccountDatasource")
// @ConfigurationProperties(prefix = "spring.datasource.merchant-account")
public HikariDataSource merchantAccountDatasource(Environment env) {
HikariConfig config= DataSourceConfigUtil.setDataSourceEnvConfig("spring.datasource.merchant-account.","spring.datasource.merchant-account.hikari.",env);
return new HikariDataSource(config);
}
@Bean(name = "merchantKycDatasource")
// @ConfigurationProperties(prefix = "spring.datasource.merchant-kyc")
public HikariDataSource merchantKycDatasource(Environment env) {
HikariConfig config= DataSourceConfigUtil.setDataSourceEnvConfig("spring.datasource.merchant-kyc.","spring.datasource.merchant-kyc.hikari.",env);
return new HikariDataSource(config);
}
}
package com.shopline.paymentacceptance.merchantservice.config;
import com.zaxxer.hikari.HikariConfig;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
/**
* @author : liangmingkun
* @date : 2022/11/29 15:31
* @Desc :
*/
public class DataSourceConfigUtil {
public static HikariConfig setDataSourceEnvConfig(String prefix, String hikariPrefix, Environment env) {
HikariConfig config = new HikariConfig();
String driver = env.getProperty(prefix + "driverClassName");
String dataSourceUrl = env.getProperty(prefix + "jdbcUrl");
String user = env.getProperty(prefix + "username");
String password = env.getProperty(prefix + "password");
String minimumIdle = env.getProperty(hikariPrefix + "minimumIdle");
String maximumPoolSize = env.getProperty(hikariPrefix + "maximumPoolSize");
String autoCommit = env.getProperty(hikariPrefix + "autoCommit");
String idleTimeout = env.getProperty(hikariPrefix + "idleTimeout");
String poolName = env.getProperty(hikariPrefix + "poolName");
String maxLifetime = env.getProperty(hikariPrefix + "maxLifetime");
String connectionTimeout = env.getProperty(hikariPrefix + "connectionTimeout");
String dataSourceClassName = env.getProperty(hikariPrefix + "type");
if (StringUtils.isNotBlank(dataSourceUrl)) {
config.setJdbcUrl(dataSourceUrl);
}
if (StringUtils.isNotBlank(user)) {
config.setUsername(user);
}
if (StringUtils.isNotBlank(password)) {
config.setPassword(password);
}
if (StringUtils.isNotBlank(driver)) {
config.setDriverClassName(driver);
}
if (StringUtils.isNotBlank(minimumIdle)) {
config.setMinimumIdle(Integer.parseInt(minimumIdle));
}
if (StringUtils.isNotBlank(maximumPoolSize)) {
config.setMaximumPoolSize(Integer.parseInt(maximumPoolSize));
}
if (StringUtils.isNotBlank(autoCommit)) {
config.setAutoCommit(Boolean.parseBoolean(autoCommit));
}
if (StringUtils.isNotBlank(idleTimeout)) {
config.setIdleTimeout(Integer.parseInt(idleTimeout));
}
if (StringUtils.isNotBlank(poolName)) {
config.setPoolName(poolName);
}
if (StringUtils.isNotBlank(maxLifetime)) {
config.setMaxLifetime(Integer.parseInt(maxLifetime));
}
if (StringUtils.isNotBlank(connectionTimeout)) {
config.setConnectionTimeout(Integer.parseInt(connectionTimeout));
}
return config;
}
}
package com.shopline.paymentacceptance.merchantservice.config;
import javax.sql.DataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.shopline.risk.compliance.persistent.SensitiveDataInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
/**
* @author : liangmingkun
* @date : 2022/11/21 10:23
* @Desc : 多数据源merchantAccount库 mybatis配置
*/
@Configuration
public class MerchantAccountDataSourceMybatisConfig {
public static final String MERCHANT_ACCOUNT_SESSION_FACTORY = "merchantAccountSqlSessionFactory";
@Autowired
private SensitiveDataInterceptor sensitiveDataInterceptor;
@Primary
@Bean(name = MERCHANT_ACCOUNT_SESSION_FACTORY)
public SqlSessionFactory merchantAccountSqlSessionFactory(@Qualifier(value="merchantAccountDatasource") DataSource merchantAccountDataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(merchantAccountDataSource);
//指定mapper位置
// factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
//分页插件
PaginationInterceptor pageInterceptor = new PaginationInterceptor();
//乐观锁插件
OptimisticLockerInterceptor optimisticLockerInterceptor = new OptimisticLockerInterceptor();
factory.setPlugins(pageInterceptor,optimisticLockerInterceptor,sensitiveDataInterceptor);
return factory.getObject();
}
@Primary
@Bean(name = "merchantAccountSqlSessionTemplate")
public SqlSessionTemplate merchantAccountSqlSessionTemplate(@Qualifier("merchantAccountSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);
return template;
}
@Primary
@Bean(name = "merchantAccountTransactionManager")
public PlatformTransactionManager merchantAccountTransactionManager(@Qualifier(value="merchantAccountDatasource") DataSource merchantAccountDataSource) {
return new DataSourceTransactionManager(merchantAccountDataSource);
}
}
package com.shopline.paymentacceptance.merchantservice.config;
import javax.sql.DataSource;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.shopline.risk.compliance.persistent.SensitiveDataInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
/**
* @author : liangmingkun
* @date : 2022/11/21 10:23
* @Desc : 多数据源merchantAccount库 mybatis配置
*/
@Configuration
public class MerchantKycDataSourceMybatisConfig {
public static final String MERCHANT_KYC_SESSION_FACTORY = "merchantKycSqlSessionFactory";
@Autowired
private SensitiveDataInterceptor sensitiveDataInterceptor;
@Bean(name = MERCHANT_KYC_SESSION_FACTORY)
public SqlSessionFactory merchantKycSqlSessionFactory(@Qualifier(value="merchantKycDatasource") DataSource merchantKycDatasource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(merchantKycDatasource);
//指定mapper位置
// factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:merchantkyc/mapper/*.xml"));
//分页插件
PaginationInterceptor pageInterceptor = new PaginationInterceptor();
//乐观锁插件
OptimisticLockerInterceptor optimisticLockerInterceptor = new OptimisticLockerInterceptor();
factory.setPlugins(pageInterceptor,optimisticLockerInterceptor,sensitiveDataInterceptor);
return factory.getObject();
}
@Bean(name = "merchantKycSqlSessionTemplate")
public SqlSessionTemplate merchantKycSqlSessionTemplate(@Qualifier("merchantKycSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);
return template;
}
@Bean(name = "merchantKycTransactionManager")
public PlatformTransactionManager merchantKycTransactionManager(@Qualifier(value="merchantKycDatasource") DataSource merchantKycDatasource) {
return new DataSourceTransactionManager(merchantKycDatasource);
}
}
附一篇@transaction分析的博客:https://blog.csdn.net/weixin_44771989/article/details/123899022
浙公网安备 33010602011771号