同样是阿里出品,大名鼎鼎的 seata
,用于分布式事务,下面是集成过程。
在开始之前首先需要看一下
一、搭建 seata
服务
- seata docker
docker-compose.yml
seata-server:
image: seataio/seata-server
container_name: seata-server
restart: always
ports:
- "8091:8091"
environment:
- SEATA_PORT=8091
- STORE_MODE=file
会在本地 8091
端口启动一个服务
二、配置 seata
- 检查
seata
的registry.conf
配置
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
省略...
确定 type = "file"
(默认是这样的)
- 检查
seata
的file.conf
配置
service {
#transaction service group mapping
vgroupMapping.my_test_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
service
块配置,默认 default.grouplist = "127.0.0.1:8091"
如果是本地连接就保持原样即可
三、项目中集成 seata
maven
依赖
<!--分布式事务 seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
- 基于
bean
的配置
/**
* Seata 集成配置
*/
@Configuration
public class DataSourceProxyConfiguration {
/**
* 针对 Hikari数据源的配置
*/
// @Bean
// @ConfigurationProperties(prefix = "spring.datasource.hikari")
// public DataSource dataSource() {
// return new HikariDataSource();
// }
/**
* 针对spring jpa的配置
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
}
- 启动配置中增加
spring:
cloud:
alibaba:
seata:
tx-service-group: tx-service-group # 这个名字可以自定义,建议相同事务的服务用同一个名称
四、在项目中使用 seata
- 事务控制方(这里是
dubbo
服务消费方)
import com.lc.cloud.alibaba.api.MoneyAccountDubboService;
import com.lc.cloud.alibaba.consumer.domain.model.MoneyRecord;
import com.lc.cloud.alibaba.consumer.domain.repository.MoneyRecordRepository;
import com.lc.cloud.alibaba.consumer.domain.service.SeataTestService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Slf4j
@RefreshScope //Nacos动态刷新配置 需要热加载的bean需要加上@RefreshScope注解,当配置发生变更的时候可以在不重启应用的前提下完成bean中相关属性的刷新
@Service
public class SeataTestServiceImpl implements SeataTestService {
@Reference
MoneyAccountDubboService moneyAccountDubboService;
@Autowired
MoneyRecordRepository moneyRecordRepository;
@GlobalTransactional
@Override
public BigDecimal use(BigDecimal useAmount, Long id) {
BigDecimal amount = moneyAccountDubboService.use(useAmount, id);
moneyRecordRepository.save(MoneyRecord.builder()
.accountId(id)
.recordAmount(useAmount)
.type(-1)
.build());
if (1 == 1) throw new RuntimeException("自定义异常");
return amount;
}
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public BigDecimal add(BigDecimal addAmount, Long id) {
BigDecimal amount = moneyAccountDubboService.add(addAmount, id);
moneyRecordRepository.save(MoneyRecord.builder()
.accountId(id)
.recordAmount(addAmount)
.type(1)
.build());
return amount;
}
@Override
public BigDecimal queryAvailableAmount(Long id) {
return moneyAccountDubboService.queryAvailableAmount(id);
}
@Override
public BigDecimal queryOutAmount(Long id) {
return moneyAccountDubboService.queryOutAmount(id);
}
}
- 被控制事务方(这里是
dubbo
服务实现方)
我在以下 serviceImpl
上套了一层来实现 dubbo
,只是增加了 org.apache.dubbo.config.annotation.Service
注解,限于篇幅这里不贴出来了
import com.lc.cloud.alibaba.provider.domain.model.MoneyAccount;
import com.lc.cloud.alibaba.provider.domain.repository.MoneyAccountRepository;
import com.lc.cloud.alibaba.provider.domain.service.MoneyAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class MoneyAccountServiceImpl implements MoneyAccountService {
@Autowired
MoneyAccountRepository moneyAccountRepository;
@Override
public BigDecimal use(BigDecimal useAmount, Long id) {
MoneyAccount account = getMoneyAccount(id);
if (BigDecimal.ZERO.compareTo(useAmount) >= 0) throw new RuntimeException("[" + id + "]消耗的金额必须大于0");
BigDecimal actualUseAmount = useAmount;
if (null == account) {
account = new MoneyAccount();
account.setAvailableAmount(BigDecimal.ZERO);
account.setOutAmount(BigDecimal.ZERO);
}
if (account.getAvailableAmount().compareTo(useAmount) < 0) actualUseAmount = account.getAvailableAmount();
account.setAvailableAmount(account.getAvailableAmount().subtract(actualUseAmount));
account.setOutAmount(account.getOutAmount().add(actualUseAmount));
moneyAccountRepository.save(account);
return actualUseAmount;
}
@Override
public BigDecimal add(BigDecimal addAmount, Long id) {
MoneyAccount account = null;//getMoneyAccount(id);
if (BigDecimal.ZERO.compareTo(addAmount) >= 0) throw new RuntimeException("[" + id + "]增加的金额必须大于0");
if (null == account) {
account = new MoneyAccount();
account.setAvailableAmount(BigDecimal.ZERO);
}
account.setAvailableAmount(account.getAvailableAmount().add(addAmount));
moneyAccountRepository.save(account);
return account.getAvailableAmount();
}
@Override
public BigDecimal queryAvailableAmount(Long id) {
return getMoneyAccount(id).getAvailableAmount();
}
@Override
public BigDecimal queryOutAmount(Long id) {
return getMoneyAccount(id).getOutAmount();
}
private MoneyAccount getMoneyAccount(Long id) {
return moneyAccountRepository.findById(id).orElse(null);
}
}
这里演示了通过一个服务自身业务来操作自己的数据库做 update
同时调用 dubbo
接口使 dubbo
接口实现方也操作自己的数据库做 update
,两个独立的数据库将通过
@GlobalTransactional
被约束在同一个事务中。
五、注意
@GlobalTransactional
不能与@SentinelResource(fallback = "fallbackHandler")
同时放在一个方法上,会导致方法内部出现异常直接由sentinel
接管(还记得集成sentinel
的时候我们配置了一个基于方法的aop
吗),seata
就无效了
到此集成完成
评论区