[从零开始的账本项目08] Seata 实现分布式事务

337 阅读4分钟

在之前的账单模块中, 有增删改查的方法. 但是在增加账单的时候, 只是简单的将账单导入数据库. 而假定该账单有绑定的账户, 则对应的账户余额也应该发生变化, 因此需要同时处理两张数据库表, 但是假设只是简单的将两个逻辑进行实现, 若是对账单的插入操作完毕后出现了异常还未执行账户的操作, 则就会出现已经写入了账单但是却没有修改账户余额的问题. 因此需要数据库的事务操作, 但是由于分布式的缘故, 因此需要分布式事务, 也就有了本片中需要使用的Seata

Seata官网
本次的账本中使用的是1.4.2的版本, 也就是截至目前的最新版.

首先在官网进行对应版本的下载.
之后则需要进行对应的配置. 本次选用的是nacos注册中心 + nacos配置中心的组合. 有关nacos的使用, 可以参照: nacos官网 基本上是打开即用的级别. 对我这样的新手十分友好.

进入正题: 首先需要引入seata的依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>
<dependency>
    <groupId>de.javakaffee</groupId>
    <artifactId>kryo-serializers</artifactId>
    <version>0.36</version>
</dependency>

这里先在starter中排除了seata-spring-boot-starter
这是因为需要避免版本的不统一, 先移除后再手动选择适合的版本进行引入(与服务器中开启的seata版本保持一致).
导入了依赖之后. 进入对应的seata的目录中, 编辑/conf/registry.conf文件进行相应的配置处理.
首先是注册中心registry板块.

type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }

本地开启的话, 最简单的就这么配即可.
其次, 是配置中心config板块:
同理可得:

  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "service.vgroupMapping.bngel_tx_group"
  }

其中需要注意的是, bngel_tx_group中的bngel换成你自定义的组名即可. 并没有特殊要求.
在文件中配完之后回到/bin目录下打开seata-server.bat(windows)即可运行.

记得要先打开nacos

这里有一个坑点, 如果显示内存不足的话可以编辑seata-server.bat文件, 修改其中的配置信息含有%JAVACMD% %JAVA_OPTS%的一行将2048改为1024或者更小即可.

运行成功后就是进行对应代码的配置.
bootstrap.yml或者application.yml中添加如下配置:

seata:
  tx-service-group: bngel_tx_group
  enable-auto-data-source-proxy: false
  service:
    vgroup-mapping:
      bngel_tx_group: default
  client:
    undo:
      log-serialization: kryo

并且新增配置类:

@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDatasource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource datasource) {
        return new DataSourceProxy(datasource);
    }

    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration globalConfiguration() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy, org.apache.ibatis.session.Configuration configuration) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        sqlSessionFactoryBean.setConfiguration(configuration);
        return sqlSessionFactoryBean.getObject();
    }
}

在主启动类上加上注解

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

就可以关闭自动的数据源配置转而使用我们自定义的seata配置.
其中由于自动的配置失效的缘故, 对应的驼峰转换也会自动关闭.
在配置类中加上了对应prefix = "mybatis.configuration"的读取. 手动打开驼峰命名, 否则后续会出现一定的问题.


在`seata`配置完成并启动之后.接着便是业务逻辑相关的数据库分布式事务的编写.
目前的需求是(当`accountId`不为空的情况下):
1. 在新增账单时, 假如是收入的账单, 对应账户的余额增多, 若是支出则减少.
2. 在删除账单时, 账户进行相应余额的修改.

首先是使用openfeign操作account模块.
通过对应的service接口创建AccountService (同consumer模块)
之后在Impl类中进行使用.

@Autowired
private BillDao billDao;

@Autowired
private AccountService accountService;

@Override
@GlobalTransactional(name = "bngelbook-bill-save", rollbackFor = Exception.class)
public Integer saveBill(Bill bill) {
    Integer result = billDao.saveBill(bill);
    if (bill.getAccountId() != null) {
        CommonResult<Account> commonResult = accountService.getAccountById(bill.getAccountId());
        if (commonResult.getCode().equals(CommonResult.SUCCESS_CODE)) {
            Account account = commonResult.getData();
            Account newAccount = new Account();
            newAccount.setId(account.getId());
            newAccount.setBalance(account.getBalance() + ((bill.getIo() == 1) ? bill.getBalance() : -bill.getBalance()));
            accountService.updateAccountById(newAccount);
        }
    }
    return result;
}

此处以保存账本为例.
判断对应的account存在后, 获取该账户, 并且进行余额的修正.
在对应的方法上方使用@GlobalTransactional注解, 开启分布式事务.

  • name: 保证唯一性即可, 自定义.
  • rollbackFor: 当发生某一异常时进行数据库回滚.Exception.class则表示只要有异常均回滚.

方法完成后, 运行Spring Boot. 与之前的方法并无二样. 可以直接使用. 就实现了对应的分布式事务处理.


欢迎各位来对我的小项目提出各种宝贵的意见, 这对我的进步非常重要, 谢谢大家.
GitHub地址: Bngel/bngelbook