【单元测试shardingsphere-jdbc的分布式事务碰到的问题集合】

项目场景:
单元测试shardingsphere-jdbc的分布式事务碰到的问题集合


问题描述

最近在学习shardingsphere,没想到刚开始就给我绊一跟头,在这里集中记录一下所遇到的问题,引以为戒的同时,希望给其他遇到相同问题的小伙伴一个解决思路。

基础准备

言归正传,最开始的时候,按照网上一些教程,我做了如下准备:
两个测试库:db2022和sakila
两张测试表:position、position_detail
由于想要测试分库分表下的分布式事务,所以两个库都有position、position_detail表
表建好了,接下来就是配置:

首先是properties文件的配置:

spring.shardingsphere.datasource.names=ds0,ds1


spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/db2022?useSSL=false
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root

spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/sakila?useSSL=false
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root

# 分片策略:database-strategy按数据库分片   以什么方式去查询:行表达式inline
spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=id
# 路由规则:按主键mod2的值路由
spring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds${id % 2}

spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.sharding-column=pid
spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.algorithm-expression=ds${pid % 2}

接下来是写一个启动类,因为测试不需要前端参与,不需要以应用的方式一直运行,所以main方法可以省略,在test里进行指定启动类即可

@SpringBootApplication
@EnableTransactionManagement
public class RunBoot {

}

然后是基于JPA的dao层代码和entity层代码,这里就不赘述了

分布式事务的部分

先写一个test类:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {RunBoot.class})
public class TestShardingTransaction {
    @Resource
    private PositionRepository positionRepository;

    @Resource
    private PositionDetailRepository positionDetailRepository;

    @Test
    @Transactional
    public void test1(){
        for (int i = 1; i <= 3; i++) {
            Position position = new Position();
            position.setName("root" + i);
            position.setSalary("1000000");
            position.setCity("beijing");
            positionRepository.save(position);

//            if (i == 3) {
//                throw new RuntimeException("人为制造异常");
//            }

            PositionDetail positionDetail = new PositionDetail();
            positionDetail.setPid(position.getId());
            positionDetail.setDescription("this is a root " + i);
            positionDetailRepository.save(positionDetail);
        }
    }
}

然后引入shardingsphere的依赖

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>

和XA的依赖

<!--XA模式 -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-xa-core</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
<!--Saga模式-->
<dependency>
    <groupId>io.shardingsphere</groupId>
    <artifactId>sharding-transaction-base-saga</artifactId>
    <version>${shardingsphere-spi-impl.version}</version>
</dependency>
<!--Seata模式-->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-base-seata-at</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>

当然,我们这次用到的只有XA模式
什么都不指定的情况下,正常插入数据库没有问题,然后我们把中间的异常代码放开
再次运行结果如图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果是符合预期的,没有配置的情况下,无法实现分布式事务的管理,此时的事务是失效的,只有最后抛出异常的那条数据没有插入,其余的数据都成功插入了数据库

XA模式的配置

测试代码形式的配置:
TransactionTypeHolder.set(TransactionType.XA);

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {RunBoot.class})
public class TestShardingTransaction {
    @Resource
    private PositionRepository positionRepository;

    @Resource
    private PositionDetailRepository positionDetailRepository;

    @Resource
    private TestService testService;

    @Test
    @Transactional
    public void test1(){
    	// 指定事务模式为XA
        TransactionTypeHolder.set(TransactionType.XA);
        for (int i = 1; i <= 3; i++) {
            Position position = new Position();
            position.setName("root" + i);
            position.setSalary("1000000");
            position.setCity("beijing");
            positionRepository.save(position);

            if (i == 3) {
                throw new RuntimeException("人为制造异常");
            }

            PositionDetail positionDetail = new PositionDetail();
            positionDetail.setPid(position.getId());
            positionDetail.setDescription("this is a root " + i);
            positionDetailRepository.save(positionDetail);
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,只有db2022数据库的数据回滚了,sakila的数据没有回滚

原因分析:

首先注意到,两个数据库的事务在结果上是独立的,因为最终报错的那条数据会写入db2022,所以db2022的事务回滚了,而写入sakila数据库的sql中并不需要回滚,所以成功写入,说明分布式事务是失效的,但是单库的事务是成功的


解决方案:

查询官方文档,发现需要配置事务管理器,于是新增以下配置类

@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);
    }
}

然后就行了?当然不会这么顺利,控制台报错

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' available: No matching TransactionManager bean found for qualifier 'transactionManager' - neither qualifier match nor bean name match!

意思是我们的事务管理器需要命名为transactionManager
于是配置代码改成如下:

@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {

    @Bean(name = "transactionManager")
    public PlatformTransactionManager txManager(final DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

这次结果是正常的,两边的数据都回滚了
但是当我想要测试一下,正常插入有没有问题的时候,人傻眼了,控制台表示插入正常,但是两个数据库一条数据都没插进去。
此时控制台打印有这么条语句:

Rolled back transaction for test: [DefaultTestContext@79e2c065 testClass = TestShardingTransaction, testInstance = dao.TestShardingTransaction@1a482e36, testMethod = test1@TestShardingTransaction, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@3a93b025 testClass = TestShardingTransaction, locations = '{}', classes = '{class com.lagou.RunBoot, class com.lagou.RunBoot}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@52f759d7, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@3ecd23d9, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@18bf3d14, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@4cf4d528], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

查了一下资料,发现事务生效的情况下,Junit测试不会真正向数据库插入数据,每次插入数据都会自动回滚掉
于是加上@Rollback(false)注解,禁用自动回滚,最终结果正常
XA以外的分布式事务貌似原生的包不支持,还要下点别的东西,如果小伙伴们想用的话就自己研究研究

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值