SpringBoot整合ShardingJdbc实现XA分布式事务
什么是分布式事务?
关于分布式事务的介绍,请参考ShardingJdbc的介绍:分布式事务
官方文档分布式事务使用示例:使用示例,点进去之后,拉到最下面即可看到 官方example。
我这里仅做一个最简单的演示:
首先说明一下数据库相关信息
- 我这里使用了两个数据源,3306mysql服务和3307mysql服务,我本地起了两个mysql服务,端口号分别是3306和3307
- 3306mysql服务创建了数据库test,3307mysql服务创建了数据库t
- 数据库test创建了表test,数据库t创建了表t_name
- 通过ShardingJdbc管理这两个数据源
关键依赖如下:
<properties>
<java.version>1.8</java.version>
<sharding-sphere.version>4.1.1</sharding-sphere.version>
</properties>
<dependencies>
<!-- ShardingJdbc SpringBootStarter -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<!-- 使用XA事务时,需要引入此模块 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<!-- 使用BASE事务时,需要引入此模块 -->
<!-- <dependency>-->
<!-- <groupId>org.apache.shardingsphere</groupId>-->
<!-- <artifactId>sharding-transaction-base-seata-at</artifactId>-->
<!-- <version>${sharding-sphere.version}</version>-->
<!-- </dependency>-->
</dependencies>
然后是application.properties文件:
# ShardingJdbc 这里设置了两个数据源 3306的test库 3307的t库 3306和3307是我本地的两个mysql服务
# 显示sql
spring.shardingsphere.props.sql.show=true
# 配置真实数据源名称分别为:ds0 ds1
spring.shardingsphere.datasource.names=ds0,ds1
# 配置第 1 个数据源 ds0
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&autoReconnect=true&failOverReadOnly=false
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
# 配置第 2 个数据源 ds1
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3307/t?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&autoReconnect=true&failOverReadOnly=false
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root
# 未配置分片规则的表将通过默认数据源定位 没有配置下面 actual-data-nodes 的表都会去ds0执行相关操作
spring.shardingsphere.sharding.default-data-source-name=ds0
#数据库的表配置 说明:两个数据库不存在分库分表这些 就是单纯的两个毫无关系的数据库 其中 test表在3306(ds0)的test库 t_name表在3307(ds1)的t库
#更多说明请参考官方文档:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/;
#历史版本配置:https://github.com/apache/shardingsphere/blob/master/docs/document/content/others/api-change-history/shardingsphere-jdbc/spring-boot-starter.en.md
spring.shardingsphere.sharding.tables.test.actual-data-nodes=ds0.test
spring.shardingsphere.sharding.tables.t_name.actual-data-nodes=ds1.t_name
准备工作做好就该写代码了,代码很少,除了启动类,只有两个类,一个配置类,一个测试类。
首先是配置类,和官方文档一模一样:
@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {
@Bean
public PlatformTransactionManager txManager(final DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
然后是启动类:
@SpringBootApplication
@Import(TransactionConfiguration.class) //注意这里:导入上面的配置类
public class ShardingjdbctxApplication {
//省略无关代码...
}
最后是测试类:
@Transactional(rollbackFor = Exception.class)
@ShardingTransactionType(TransactionType.XA)
@GetMapping("test")
public void test() {
TransactionType txType0 = jdbcTemplate.execute("INSERT INTO t_name (name) VALUES (?)", (PreparedStatementCallback<TransactionType>) preparedStatement -> {
//t_name表只有主键id和name两个字段
preparedStatement.setString(1, UUID.randomUUID().toString().replaceAll("-", ""));
preparedStatement.executeUpdate();
return TransactionTypeHolder.get();
});
System.out.println("txType0====>>>" + txType0);
TransactionType txType1 = jdbcTemplate.execute("UPDATE test SET age = ? WHERE id = 1", (PreparedStatementCallback<TransactionType>) preparedStatement -> {
//test表只有主键id和age两个字段
preparedStatement.setInt(1, 18);
preparedStatement.executeUpdate();
return TransactionTypeHolder.get();
});
System.out.println("txType1====>>>" + txType1);
//抛出异常 事务回滚
int a = 10 / 0;
}
如此,便会回滚事务。
此外,一些疑惑:
- 不添加
@ShardingTransactionType(TransactionType.XA)
注解,或者该注解设置成 TransactionType.LOCAL
也会回滚事务
-
rollbackFor
设置成 NullPointerException
也会回滚事务,但是上面抛出的分明是 ArithmeticException: / by zero
具体原因,暂时不知,待进一步了解其原理再来说明。也希望路过的大神不吝赐教~