SpringJTA分布式事务实现
1.概述
Java Transaction API,通常称为JTA,是⽤于管理 Java中的事务的API 。它允许我们以资源⽆关的⽅式启动,提交和回滚事务。
根据⽤于管理事务的底层实现,Spring中的事务策略可以分为两个主要部分:
单连接器策略(相当于本地事务管理器) - 底层技术使⽤单连接器。例如,JDBC使⽤连接级事务、Hibernate以及JDO使⽤会话级事务。可以应⽤使⽤AOP和的声明式事务管理。
多连接器策略(相当于全局事务管理器) - 底层技术具有使⽤多个连接器的能⼒。当有这⽅⾯需求时,JTA是最好的选择。此策略需要启⽤JTA的数据源实例。JBossTS、Atomikos、Bitronix都是开源的JTA实现。
JTA的真正强⼤之处在于它能够在单个事务中管理多个资源(如数据库,消息服务)。
在本⽂中,我们将从概念层⾯了解JTA,并了解业务代码通常是如何与JTA交互。
2.通⽤接⼝和分布式事务
JTA提供了对业务代码的事务控制(开始,提交和回滚)的抽象。
如果没有这种抽象,我们必须处理每种资源类型的各个API。
例如,我们需要处理JDBC资源。同样,JMS资源可能具有类似但不兼容的模型。
通过JTA,我们可以以⼀致和协调的⽅式管理不同类型的多种资源。
作为API,JTA定义了由事务管理器实现的接⼝和语义 。实现由Atomikos和Bitronix等库提供。
3.⽰例项⽬
本例⼦模拟了银⾏应⽤的⼀个⾮常简单的转账业务。我们有两个服务:银⾏账户服务BankAccountService 和操作⾏为审计服务AuditService,它们使⽤了两个不同的数据库。
数据库采⽤的是JAVA内置数据库HSQLDB,这些独⽴的数据库需要在事务开始,提交或回滚时进⾏协调。
JTA事务管理器采⽤Bitronix。
我们的⽰例项⽬使⽤Spring Boot来简化配置:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-bitronix</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
</dependencies>
在服务启动时,建⽴2个数据源:accountDb,auditDb:
@Bean("dataSourceAccount")
public DataSource dataSource() throws Exception {
return createHsqlXADatasource("jdbc:hsqldb:mem:accountDb");
}
@Bean("dataSourceAudit")
public DataSource dataSourceAudit() throws Exception {
return createHsqlXADatasource("jdbc:hsqldb:mem:auditDb");
}
在每个测试⽅法之前,我们使⽤脚本分别在每个库下创建表:ACCOUNT和AUDIT_LOG,并初始化⼀些数据:
ID BALANCE
a00000011000
a00000022000
4.声明性事务界定
在JTA中处理事务的第⼀种⽅法是使⽤@Transactional注解。
让我们⽤@Transactional注解服务⽅法 executeTranser()。 这表明事务管理器开始事务:
@Transactional
public void executeTransfer(String fromAccontId, String toAccountId,
BigDecimal amount) {
auditService.log(fromAccontId, toAccountId, amount);
BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
if (balancepareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("余额不⾜!");
}
}
这⾥ executeTranser()⽅法调⽤了2个不同的服务:AccountService和AuditService。这2个服务使⽤2个不同的数据库。
当 executeTransfer()返回时,该事务管理器识别出它是事务的结束,将作⽤于两个数据库。
在⽅法结束时,如果转账⼈的资⾦不⾜, executeTransfer()会检查帐户余额并抛出 RuntimeException异常。
4.1没有异常全部提交
场景1:当从a0000001账户给a0000002账户转账500元时,由于执⾏⾦额⼩于余额,⼀切正常。⾏为审计表⾥增加⼀条记录。
@Test
public void givenAnnotationTx_whenNoException_thenAllCommitted()
throws Exception {
BigDecimal.valueOf(500));
assertThat(accountService.balanceOf("a0000001"))
.isEqualByComparingTo(BigDecimal.valueOf(500));
assertThat(accountService.balanceOf("a0000002"))
.isEqualByComparingTo(BigDecimal.valueOf(2500));
TransferLog lastTransferLog = auditService.lastTransferLog();
assertThat(lastTransferLog).isNotNull();
FromAccountId()).isEqualTo("a0000001");
ToAccountId()).isEqualTo("a0000002");
Amount())
.isEqualByComparingTo(BigDecimal.valueOf(500));
}
4.2出现异常进⾏回滚
场景2:当从a0000002账户给a0000001账户转账10000元时,由于执⾏⾦额⼤于余额,抛出异常。两个数据库进⾏回滚,账户余额不变,⾏为审计表没有数据。
@Test
spring aop应用场景public void givenAnnotationTx_whenException_thenAllRolledBack()
throws Exception {
assertThatThrownBy(() -> {
BigDecimal.valueOf(100000));
}).hasMessage("余额不⾜!");
assertThat(accountService.balanceOf("a0000001"))
.isEqualByComparingTo(BigDecimal.valueOf(1000));
assertThat(accountService.balanceOf("a0000002"))
.isEqualByComparingTo(BigDecimal.valueOf(2000));
assertThat(auditService.lastTransferLog()).isNull();
}
5.编程性事务界定
另⼀种控制JTA事务的⽅法是通过调⽤ ansaction.UserTransaction以编程⽅式实现。
public void executeTransferProgrammaticTx(String fromAccontId,
String toAccountId, BigDecimal amount) throws Exception {
userTransaction.begin();
auditService.log(fromAccontId, toAccountId, amount);
BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
if (balancepareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("余额不⾜!");
} else {
userTransactionmit();
}
}
在我们的⽰例中,begin()⽅法启动了⼀个新事务。如果余额验证失败,调⽤rollback(),它将回滚两个数据库。否则,调⽤
commit() 会将更改提交给两个数据库。
需要注意的是 commit()和 rollback()都 结束当前事务。
6.总结
在本⽂中,我们讨论了JTA试图解决的问题。通过⽰例⼯程说明了使⽤注释和编程⽅式来控制事务,涉及需要在单个事务中协调2个事务资源。
⽰例完整代码可以在上到。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论