以前,笔者写过一篇博客,支付宝DTS方案,当然,只是仅仅是简单讨论了下分布式事务的解决方案。PS:笔者看了下相关评论,发现由于太简单,被不少人Diss了一通。

最近,笔者在自己的工程上,试图一次性解决分布式事务问题。

笔者自身的工程,目前是Springboot作为基本框架,以JPA作为数据库操作工作。笔者首先想到的,是如何利用现有框架,以及成熟的方案,做一个分布式解决方案。

然后,查了一段时间,发现JTA解决方案。

然后发现Atomikos,就用Atomikos作为核心解决方案。

发现JTA解决分布式方案的思想,是对事务进行托管。因为单独一个事务,本身是ACID的;两个事务,却不能做到同时ACID。可行方案是TCC或两阶段提交(或三阶段提交)。而对事务进行托管,实际上是让托管程序完成TCC或两阶段提交。

关于分布式解决方案,如果博友想学习,可以参考田守枝的技术博客(分布式事务概述_分布式事务教程_田守枝Java技术博客本文介绍了分布式事务的基础概念,包括本地事务、DTP模型,XA两阶段提交,基于BASE理论的柔性事务等http://www.tianshouzhi.com/api/tutorials/distributed_transaction/383),笔者认为是关于分布式事务里极好的博客。大家可以对分布式事务进行整体认识。

但本篇博客的精髓,在于实现Springboot框架下的分布式事务。

笔者参考了大部分技术博客,发现大部分博客介绍JTA,会把JTA作为一个Repository方式进行增删改查。这种方式的优点是可以简单完成数据库的存储,但缺点是不能较好使用JPA。

一个理想的方法是:单独使用JPA,让JPA可以自用操作数据库。

笔者的实现步骤是:

第一步,更改pom.xml文件,添加依赖库。

<!-- distributed transaction support -->
<dependency>
	<groupId>com.atomikos</groupId>
	<artifactId>transactions-jta</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
	<groupId>com.atomikos</groupId>
	<artifactId>transactions-jdbc</artifactId>
</dependency>

第二步,更改Persistence.xml文件;该文件可以用EntityManagerFactory生成EntityManager。

<!--定义持久单元 -->
<persistence-unit name="xxx" transaction-type="JTA">
     <property name="org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform" value="路径.AtomikosJtaPlatform" />

注意:这里面需要做的是:

1)transaction-type改为JTA;

2)在hibernate4.3以后,JtaPlatform的路径为org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform,之前为hibernate.transaction.jta.platform;

3)AtomikosJtaPlatform的路径必须存在。

第三步,生成AtomikosJtaPlatform文件。

public class AtomikosJtaPlatform extends AbstractJtaPlatform {
    private static final long serialVersionUID = 1L;

    static TransactionManager transactionManager;
    static UserTransaction transaction;

    @Override
    protected TransactionManager locateTransactionManager() {
        return transactionManager;
    }

    @Override
    protected UserTransaction locateUserTransaction() {
        return transaction;
    }
}

注意transactionManager即对事务托管的管理器;transaction对应不同事务。此方案即可看到事务的管理。

第四步,编写可执行程序。

EntityManager em0 = null;  //调用自己的方法生成
EntityManager em1 = null;  //调用自己的方法生成

try {
    //选用任意一个EntityManager的事务即可
    em0.getTransaction().begin();
    
    //不同事务操作
    em0.merge(entity0);
    em1.merge(entity1);

    //选用原有EntityManager提交
    em0.getTransaction().commit();
} catch (Exception e) {
    e.printStackTrace();

    try {
        //正确状态进行事务回滚
        if (em0.getTransaction().isActive() && em0.getTransaction().getRollbackOnly()) {
            em0.getTransaction().rollback();
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
} finally {
    if (em0 != null) {
        em0.close();
        em0 = null;
    }

    if (em1 != null) {
        em1.close();
        em1 = null;
    }
}

在实际执行中,打开一个事务即可。

调用该方案之后,发现运行出现如下错误:

Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine suitable jdbc url

经过多次试验以后,发现该问题是由JTA库造成的。

解决方案如下:

在Springboot启动对应的yml或propeties库中加入如下的配置。(Properties文件需要换格式修改)

spring:
  # JTA
  jta:
    enabled: false

该方法可以让JTA不随Springboot启动,从而让该错误不发生。

使用者可以在自己的程序中,灵活使用JPA。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐