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

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

Seata官网

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

首先在官网进行对应版本的下载.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
xml复制代码<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板块.

1
2
3
4
5
6
7
8
9
10
11
conf复制代码type = "nacos"

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

本地开启的话, 最简单的就这么配即可.

其次, 是配置中心config板块:

同理可得:

1
2
3
4
5
6
7
8
9
10
ini复制代码  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中添加如下配置:

1
2
3
4
5
6
7
8
9
yaml复制代码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

并且新增配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码@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();
}
}

在主启动类上加上注解

1
java复制代码@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

就可以关闭自动的数据源配置转而使用我们自定义的seata配置.

其中由于自动的配置失效的缘故, 对应的驼峰转换也会自动关闭.

在配置类中加上了对应prefix = "mybatis.configuration"的读取. 手动打开驼峰命名, 否则后续会出现一定的问题.

seata配置完成并启动之后.接着便是业务逻辑相关的数据库分布式事务的编写.

目前的需求是(当accountId不为空的情况下):

  1. 在新增账单时, 假如是收入的账单, 对应账户的余额增多, 若是支出则减少.

  2. 在删除账单时, 账户进行相应余额的修改.

首先是使用openfeign操作account模块.

通过对应的service接口创建AccountService (同consumer模块)

之后在Impl类中进行使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码@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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%