侧边栏壁纸
博主头像
术业有道之编程博主等级

亦是三月纷飞雨,亦是人间惊鸿客。亦是秋霜去叶多,亦是风华正当时。

  • 累计撰写 99 篇文章
  • 累计创建 50 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

springcloud alibaba 之 seata

Administrator
2020-09-01 / 0 评论 / 0 点赞 / 59 阅读 / 8641 字

同样是阿里出品,大名鼎鼎的 seata ,用于分布式事务,下面是集成过程。

项目源码

在开始之前首先需要看一下

一、搭建 seata 服务

seata-server:
  image: seataio/seata-server
  container_name: seata-server
  restart: always
  ports:
    - "8091:8091"
  environment:
    - SEATA_PORT=8091
    - STORE_MODE=file

会在本地 8091 端口启动一个服务

二、配置 seata

  • 检查 seataregistry.conf 配置
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "file"

省略...

确定 type = "file" (默认是这样的)

  • 检查 seatafile.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 就无效了

到此集成完成

个人公众号

0

评论区