在探事务-JTA处理分布式事务

事务在后端中是占着很重要的地位,也是一个比较难处理的地方。以我现在所知可以将事务分为两大类,一类本地事务,一类分布式事务。分类标准也很简单,本地事务就是一个系统只用一个数据库,且只在一个项目下。分布式事务就是一个系统用多个数据库,或一个项目用了多个数据库。本篇呢就简单记录下在一个项目下用多个数据库的事务处理。

一、初识JTA

JTA,即Java Transaction API,JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。JDBC驱动程序的JTA支持极大地增强了数据访问能力。

这是来自百度百科的解释。他的根本目标就是为了多数据库下的事务统一,维护ACID特性。

要使用JTA事务,必须使用XADataSource来产生数据库连接,产生的连接为一个XA连接。

这是使用JTA事务的前提,数据源必须是XADataSource。在这个条件的约束下一些我们常用的数据库连接池(如:hikaricp,c3p0,dbcp)就没法使用咯。但我们熟知的阿里还是很强的,阿里的druid提供了对XADataSource的支持。

JTA只是一套接口定义,具体实现要靠各个厂商的支持。本次使用的是Atomikos

二、在spring中添加JAT依赖

使用maven,添加pom依赖

1、SSM

<!--jta支持-->
<dependency>
  <groupId>com.atomikos</groupId>
  <artifactId>transactions-jdbc</artifactId>
  <version>4.0.6</version>
</dependency>

<dependency>
  <groupId>javax.transaction</groupId>
  <artifactId>javax.transaction-api</artifactId>
  <version>1.3</version>
</dependency>

2、springboot

<!--全局事务 分布式事务1 多数据源-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

其实需要很多的依赖,这里transaction-jdbc会自动依赖其他需要的。

三、进行JTA配置

之前也说过了要支持JAT事务,一般的数据源是不能使用的,所以一般的数据库连接池就不能使用了。本次我也没有使用阿里家的数据库连接池,直接使用Atimikos提供的数据库连接池。

1、SSM项目配置

  • 多数据源配置
  <!-- 一号数据源配置 -->
  <bean id="ds01" class="com.atomikos.jdbc.AtomikosDataSourceBean">
    <property name="xaDataSource">
      <bean class="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
        <property name="url" value="jdbc:mysql:///multi_ds01"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
      </bean>
    </property>
    <property name="poolSize" value="2"/>
    <property name="maxPoolSize" value="10"/>
    <property name="uniqueResourceName" value="ds01xa"/> <!-- 必填项,且每个数据源要不一样 -->
  </bean>

  <!-- 二号数据源配置 -->
  <bean id="ds02" class="com.atomikos.jdbc.AtomikosDataSourceBean">
    <property name="xaDataSource">
      <bean class="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
        <property name="url" value="jdbc:mysql:///multi_ds02"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
      </bean>
    </property>
    <property name="poolSize" value="2"/>
    <property name="maxPoolSize" value="10"/>
    <property name="uniqueResourceName" value="ds02xa"/>
  </bean>
  • 全局事务配置
  <!-- jta全局事务配置 -->
  <bean id="userTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
    <property name="transactionTimeout" value="30000"/>
  </bean>

  <bean id="userTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
        depends-on="userTransaction" init-method="init" destroy-method="close">
    <property name="forceShutdown" value="false"/> <!-- 这里我也不知道干嘛,看很多教程都有 -->
  </bean>

  <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransaction" ref="userTransaction"/>
    <property name="transactionManager" ref="userTransactionManager"/>
  </bean>
  
  <tx:annotation-driven transaction-manager="jtaTransactionManager"/>
  • 使用mybatisplus,sqlsession工厂具体配置如下
  <!--一号数据源下的sql session工厂-->
  <bean id="ssf01" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="ds01"/>
    <property name="typeAliasesPackage" value="cn.lkangle.entity"/>
  </bean>
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="cn.lkangle.mapper.ds01"/>
    <property name="sqlSessionFactoryBeanName" value="ssf01"/>
  </bean>

  <!--二号数据源下的sql session-->
  <bean id="ssf02" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="ds02"/>
    <property name="typeAliasesPackage" value="cn.lkangle.entity"/>
  </bean>
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="cn.lkangle.mapper.ds02"/>
    <property name="sqlSessionFactoryBeanName" value="ssf02"/>
  </bean>

2、springboot配置

  • 多数据源以及sqlsession工厂配置
@Configuration
@MapperScan(basePackages = "cn.lkangle.multids01.mappers.mapper01",
        sqlSessionFactoryRef = "ds01_sqlSession")
@MapperScan(basePackages = "cn.lkangle.multids01.mappers.mapper02",
        sqlSessionFactoryRef = "ds02_sqlSession")
public class MultiDSPlusConfig {

//  ----------- 主数据源 -----------
    @Bean("ds01")
    @Primary
    public DataSource data1Source() {
        MysqlXADataSource dataSource = new MysqlXADataSource();
        dataSource.setURL("jdbc:mysql:///multi_ds01");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setPinGlobalTxToPhysicalConnection(true);
        AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
        dataSourceBean.setXaDataSource(dataSource);
        dataSourceBean.setMaxPoolSize(10);
        dataSourceBean.setUniqueResourceName("ds01_datasource");
        dataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.Driver");
        return dataSourceBean;
    }

//  ------------- 第二数据源 -------------
    @Bean("ds02")
    public DataSource data2Source() {
        MysqlXADataSource dataSource = new MysqlXADataSource();
        dataSource.setURL("jdbc:mysql:///multi_ds02");
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setPinGlobalTxToPhysicalConnection(true);
        AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
        dataSourceBean.setXaDataSource(dataSource);
        dataSourceBean.setMaxPoolSize(10);
        dataSourceBean.setUniqueResourceName("ds02_datasource");
        dataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.Driver");
        return dataSourceBean;
    }

//  ---------- 不同数据源对应的 sql session 工厂 -----------
    @Bean("ds01_sqlSession")
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("ds01") DataSource dataSource) throws IOException {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        ResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
        Resource[] resources = loader.getResources("classpath:mapper/ds01/*.xml");
        bean.setMapperLocations(resources);
        return bean;
    }

    @Bean("ds02_sqlSession")
    public MybatisSqlSessionFactoryBean sqlSession2FactoryBean(@Qualifier("ds02") DataSource dataSource) throws IOException {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        ResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
        Resource[] resources = loader.getResources("classpath:mapper/ds02/*.xml");
        bean.setMapperLocations(resources);
        return bean;
    }
}
  • 开启全局JTA事务配置
@Configuration
@EnableTransactionManagement
public class GlobalTxConfig {

    @Bean
    public UserTransaction userTransaction() throws SystemException {
        UserTransactionImp transactionImp = new UserTransactionImp();
        transactionImp.setTransactionTimeout(20000);
        return transactionImp;
    }

    @Bean(initMethod = "init", destroyMethod = "close")
    public UserTransactionManager userTransactionManager() {
        return new UserTransactionManager();
    }

    @Bean
    public JtaTransactionManager transactionManager(@Qualifier("userTransaction") UserTransaction userTransaction,
                                                    @Qualifier("userTransactionManager") UserTransactionManager userTransactionManager) {
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }
}

已上即完成了JTA事务管理的配置。

四、测试

完成已上的各种配置后,我们就可以像使用本地事务那样实现全局事务的处理了。这里就是只贴出service层的代码了。

@Service
public class UserService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private Ds01Mapper ds01Mapper;

    @Autowired
    private Ds02Mapper ds02Mapper;
    
    // 声明式
    @Transactional
    public int insert1(MUser user) {
        int ds1 = ds01Mapper.insert(user);
        int ds2 = ds02Mapper.insert(user);

        int p = 9 / user.getNum();

        return ds1 + ds2;
    }

    // 编程式
    public int insert(MUser user) {
        return transactionTemplate.execute(status -> {

            System.out.println(status);

            int ds1 = ds01Mapper.insert(user);
            int ds2 = ds02Mapper.insert(user);

            int p = 9 / user.getNum();

            return ds1 + ds2;
        });
    }
    ......
}

当num为0时,产生异常事务回滚。

五、总结

捣鼓了好久才调通,在这里记录下。出现的问题有1、再配置全局事务的同时,还配置了jdbc的事务,这是无法使用的。2、在Controller中是用事务。不好意思也没成功,主要是ssm中配置的时候事务只对非controller层中有效

源码地址JTA样例

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容