Druid阅读(八)XA环境搭建

目录

1 什么是XA事务

2 XA搭建说明

3 双数据源管理

4 设置主从

5 运行


1 什么是XA事务

XA是X/Open DTP组织(X/Open DTP group)定义的两阶段提交协议,XA被许多数据库(如Oracle、DB2、SQL Server、MySQL)和中间件等工具(如CICS 和 Tuxedo).本地支持 。
X/Open DTP模型(1994)包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)、通信资源管理器(CRM)四部分。

在这个模型中,通常事务管理器(TM)是交易中间件,资源管理器(RM)是数据库,通信资源管理器(CRM)是消息中间件。

一般情况下,某一数据库无法知道其它数据库在做什么,因此,在一个DTP环境中,交易中间件是必需的,由它通知和协调相关数据库的提交或回滚。而一个数据库只将其自己所做的操作(可恢复)影射到全局事务中。

XA就是X/Open DTP定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。

2 XA搭建说明

一个应用有时候需要同时连接多个数据库,并且多个数据库间的表操作还需要管理他们间事务的一致性。所以接下来将研究下分布式事务Druid是如何支持的。这里将集成Druid+Atomikos来搭建一个双数据源的XA事务。为什么要引入Atomikos呢?单靠Druid无法管理。

搭建的示例工程目录结构如下:

  • MybatisConfigurer1配置数据源1并交给mybatis的sqlsession1管理
  • MybatisConfigurer2配置数据源2并交给mybatis的sqlsession2管理
  • DruidTestMapper执行操作数据库1的表Druid_test的数据
  • SoulTestMapper操作数据库2的表soul_test的数据
  • DruidTestService连接双数据源做业务逻辑操作
  • DemoDruidApplicationTests单元测试

3 双数据源管理

数据源管理1,配置的代码如下:

/**
 * 配置扫描com.example.demo_druid_xa.mapper.ds1目录下的mapper将连接数据源1操作
 * 配置sqlSessionFactoryRef为sqlSessionTemplate1
 */
@Configuration
@MapperScan(basePackages = {"com.example.demo_druid_xa.mapper.ds1"},sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MybatisConfigurer1 {

    /**
     * jdbc.ds1获取配置信息,初始化druidDataSource1
     * @return
     */
    @Bean(name="druidDataSource1")
    @ConfigurationProperties(prefix = "jdbc.ds1")
    public DruidXADataSource dataSource0(){
        DruidXADataSource dataSource = new DruidXADataSource();
        return dataSource;
    }

    /**
     * 实例化AtomikosDataSourceBean,并且set Druid初始化的DruidXADataSource
     * @param druidDataSource1
     * @return
     */
    @Primary
    @Bean(name = "dataSource1")
    public AtomikosDataSourceBean dataSource(@Qualifier("druidDataSource1") DruidXADataSource druidDataSource1){
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();

        try {
            druidDataSource1.setFilters("stat");
            xaDataSource.setXaDataSource(druidDataSource1);
            xaDataSource.setMaxPoolSize(10);
            xaDataSource.setMinPoolSize(5);
            xaDataSource.setUniqueResourceName("dataSource1");
        } catch (SQLException e) {
            System.out.println("dataSource1 init error!"+e);
        }
        return xaDataSource;
    }


    /**
     * 将AtomikosDataSourceBean交给SqlSessionFactory
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean(name = "sqlSessionFactory1")
    public SqlSessionFactory sqlSessionFactory1(@Qualifier("dataSource1") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }


    /**
     * 将sqlSessionFactory交给SqlSessionTemplate管理
     * @param sqlSessionFactory
     * @return
     */
    @Bean(name = "sqlSessionTemplate1")
    public SqlSessionTemplate sqlSessionTemplate1(
            @Qualifier("sqlSessionFactory1") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

数据源管理2,配置的代码如下:

/**
 * 配置扫描com.example.demo_druid_xa.mapper.ds2目录下的mapper将连接数据源2操作
 * 配置sqlSessionFactoryRef为sqlSessionTemplate2
 */
@Configuration
@MapperScan(basePackages = {"com.example.demo_druid_xa.mapper.ds2"},sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MybatisConfigurer2 {

    /**
     * jdbc.ds1获取配置信息,初始化druidDataSource2
     * @return
     */
    @Bean(name="druidDataSource2")
    @ConfigurationProperties(prefix = "jdbc.ds2")
    public DruidXADataSource dataSource0(){
        DruidXADataSource dataSource = new DruidXADataSource();
        return dataSource;
    }

    /**
     * 实例化AtomikosDataSourceBean,并且set Druid初始化的DruidXADataSource
     * @param druidDataSource2
     * @return
     */
    @Bean(name = "dataSource2")
    public AtomikosDataSourceBean dataSource(@Qualifier("druidDataSource2") DruidXADataSource druidDataSource2){
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();

        try {
            druidDataSource2.setFilters("stat");
            xaDataSource.setXaDataSource(druidDataSource2);
            xaDataSource.setMaxPoolSize(10);
            xaDataSource.setMinPoolSize(5);
            xaDataSource.setUniqueResourceName("dataSource2");
        } catch (SQLException e) {
            System.out.println("dataSource2 init error!"+e);
        }
        return xaDataSource;
    }


    /**
     * 将AtomikosDataSourceBean交给SqlSessionFactory
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean(name = "sqlSessionFactory2")
    public SqlSessionFactory sqlSessionFactory2(@Qualifier("dataSource2") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    /**
     * 将sqlSessionFactory交给SqlSessionTemplate管理
     * @param sqlSessionFactory
     * @return
     */
    @Bean(name = "sqlSessionTemplate2")
    public SqlSessionTemplate sqlSessionTemplate2(
            @Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

 application.properties配置文件如下:

#---------------------使用durid连接池
jdbc.datasources=ds1,ds2

jdbc.ds1.url=jdbc:mysql://localhost:3306/shenyu?useUnicode=true&characterEncoding=utf8
jdbc.ds1.username=root
jdbc.ds1.password=123456
jdbc.ds1.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.ds1.initialSize=5
jdbc.ds1.minIdle=10
jdbc.ds1.maxActive=20
jdbc.ds1.maxWait=6000
jdbc.ds1.timeBetweenEvictionRunsMillis=2000
jdbc.ds1.minEvictableIdleTimeMillis=600000
jdbc.ds1.maxEvictableIdleTimeMillis=900000
jdbc.ds1.validationQuery=select 1
jdbc.ds1.testWhileIdle=true
jdbc.ds1.testOnBorrow=false
jdbc.ds1.testOnReturn=false
jdbc.ds1.keepAlive=true
jdbc.ds1.phyMaxUseCount=1000
jdbc.ds1.filters=stat

jdbc.ds2.url=jdbc:mysql://localhost:3306/soul?useUnicode=true&characterEncoding=utf8
jdbc.ds2.username=root
jdbc.ds2.password=123456
jdbc.ds2.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.ds2.initialSize=5
jdbc.ds2.minIdle=10
jdbc.ds2.maxActive=20
jdbc.ds2.maxWait=6000
jdbc.ds2.timeBetweenEvictionRunsMillis=2000
jdbc.ds2.minEvictableIdleTimeMillis=600000
jdbc.ds2.maxEvictableIdleTimeMillis=900000
jdbc.ds2.validationQuery=select 1
jdbc.ds2.testWhileIdle=true
jdbc.ds2.testOnBorrow=false
jdbc.ds2.testOnReturn=false
jdbc.ds2.keepAlive=true
jdbc.ds2.phyMaxUseCount=1000
jdbc.ds2.filters=stat

4 设置主从

由 @Primary来配置主从,@Primary为Spring注解,Bean配置了该注解后,会被默认优先选择,使用如下,这里配置了数据源1为主数据源。

5 运行

Mapper代码如下:

public interface DruidTestMapper {
    @Insert("insert into druid_test values ('2','2')")
    void insertOne();
}
public interface SoulTestMapper {

    @Insert("insert into soul_test values ('2')")
    void insertOne();
}
@Service
public class DruidTestService {

    @Autowired
    private DruidTestMapper druidTestMapper;

    @Autowired
    private SoulTestMapper soulTestMapper;

    @Transactional(rollbackFor = Exception.class)
    public void insert(){
        druidTestMapper.insertOne();
        soulTestMapper.insertOne();
    }
}
@SpringBootTest
class DemoDruidApplicationTests {

	@Autowired
	private DruidTestService druidTestService;

	@Test
	void test() {
		druidTestService.insert();
	}

}

run DemoDruidApplicationTests,运行成功。

查看数据库两张表结果,已插入成功2。

 

再来一个失败测试事务的回滚:

public interface DruidTestMapper {
    @Insert("insert into druid_test values ('3','3')")
    void insertOne();
}
// 错误代码
public interface SoulTestMapper {

    @Insert("insert into soul_test values x ('3')")
    void insertOne();
}
@Service
public class DruidTestService {

    @Autowired
    private DruidTestMapper druidTestMapper;

    @Autowired
    private SoulTestMapper soulTestMapper;

    @Transactional(rollbackFor = Exception.class)
    public void insert(){
        // 第一步执行成功
        druidTestMapper.insertOne();
        // 第二步执行失败
        soulTestMapper.insertOne();
    }
}

查看结果,发现两个数据库都没数据插入,说明事务已回滚:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值