SpringBoot 集成 atomikos 实现分布式事务

在项目中当涉及到多数据源操作的时候,例如增加、更新、删除。我们要采用一些额外的手段来进行事务的操作,常规的例如:

1、XA事务:通过两阶段进行事务确认。可以用seata

2、TCC事务:可以通过补偿的方式来进行事务的逆向处理。可以用seata,Hmily或手动进行实现

3、最大努力通知:不保证强一致性,但是基于最终一致性通过异步或者后续补偿机制来完成事务。例如rocketMQ.

本文是介绍的一种基于XA事务的方式,采用轻量级的atomikos 的方式来保证动态多数据源的事务。

引入maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

 配置项:

@Configuration
//表示通过 aop 框架暴露改代理对象,AopContext 能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan(basePackages = "xx.xx.多数据源动态实现.mapper" , sqlSessionTemplateRef = "sqlSessionTemplate")
    public class ApplicationConfig {
}

AtomikosConfig 

JTA 事务配置类:Java Transaction API。

定义了 PlatformTransactionManager 实际是 JtaTransactionManager。

@Configuration
public class AtomikosConfig {

    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws SystemException {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({"userTransaction", "atomikosTransactionManager"})
    public PlatformTransactionManager transactionManager() throws SystemException {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }

}

 DruidConfig

  1. 首先定义了两个数据源Bean(这里有几个数据源,就定义几个Bean)

 

 @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.master.";
        return getDataSource(env, prefix, MASTER);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.slave.";
        return getDataSource(env, prefix, SLAVE);
    }
    protected DataSource getDataSource(Environment env, String prefix, String dataSourceName)
    {
        Properties prop = build(env, prefix);//获取配置信息
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName(dataSourceName);
        ds.setXaProperties(prop);
        return ds;
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(MASTER, masterDataSource);
        setDataSource(targetDataSources, SLAVE, "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }


//下面是结合 AbstractRoutingDataSource 实现多数据源的代码,具体参考源码

    /**
     * 设置数据源
     *
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }

MybatisConfig

public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception
    {
//        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        String configLocation = env.getProperty("mybatis.config-location");
//        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
//        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sessionFactory.getObject();
    }

    @Bean(name = "sqlSessionFactoryMaster")
    public SqlSessionFactory sqlSessionFactoryMaster(Environment env, @Qualifier("masterDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }

    @Bean(name = "sqlSessionFactorySlave")
    public SqlSessionFactory sqlSessionFactorySlave(Environment env, @Qualifier("slaveDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }

    @Bean(name = "sqlSessionTemplate")
    public DynamicSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryMaster") SqlSessionFactory factoryMaster,
                                                        @Qualifier("sqlSessionFactorySlave") SqlSessionFactory factorySlave) throws Exception
    {
        Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
        sqlSessionFactoryMap.put(DruidConfig.MASTER, factoryMaster);
        sqlSessionFactoryMap.put(DruidConfig.SLAVE, factorySlave);

        DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster);
        customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
        return customSqlSessionTemplate;
    }

 自定义SqlSessionTemplate

这里初始化会结合 MybatisConfig :有几个数据源就会初始化几个 SqlSessionFactory 具体执行语句的时候就会使用具体的 SqlSessionFactory 去执行。 

public class DynamicSqlSessionTemplate extends SqlSessionTemplate {

    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;
    private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
    private SqlSessionFactory defaultTargetSqlSessionFactory;


    @Override
    public SqlSessionFactory getSqlSessionFactory()
    {
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys
                .get(DynamicDataSourceContextHolder.getDataSourceType());
        if (targetSqlSessionFactory != null)
        {
            return targetSqlSessionFactory;
        }
        else if (defaultTargetSqlSessionFactory != null)
        {
            return defaultTargetSqlSessionFactory;
        }
        return this.sqlSessionFactory;
    }


    private class SqlSessionInterceptor implements InvocationHandler
    {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            final SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
                    DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
            try
            {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory()))
                {
                    sqlSession.commit(true);
                }
                return result;
            }
            catch (Throwable t)
            {
                Throwable unwrapped = unwrapThrowable(t);
                if (DynamicSqlSessionTemplate.this.exceptionTranslator != null
                        && unwrapped instanceof PersistenceException)
                {
                    Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
                            .translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null)
                    {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            }
            finally
            {
                closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }
}

 多数据源配置:自行根据项目所用的进行配置

@Override
    @Transactional
    public Map testDataSource() {
        SpringUtils.getAopProxy(this).insertA();
        SpringUtils.getAopProxy(this).insertB();
        int i=10/0;
        return new HashMap();
    }

    @DataSource(value = DataSourceType.slave)
    public void insertA(){
        dynamicTestMapper.dynamicTestA();
    }

    @DataSource(value = DataSourceType.master)
    public void insertB(){
        dynamicTestMapper.dynamicTestB();
    }

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值