多数据源下@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

 

posted @   klm-kain  阅读(606)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!
点击右上角即可分享
微信分享提示