多数据源下@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * 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的事务管理器,就相当于没回滚。
详细的多数据源实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 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
分类:
Spring
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!