nacos环境下seata配置

seata是笔者遇到技术栈中配置相对麻烦的其中一个,按官方案例不一定成功,所以这里记录一下:

本文选择seata 1.3.0版本,项目添加依赖也是这个版本。Release v1.3.0 · seata/seata · GitHub

配置seata服务端: 下载seata-server和source code。 解压ource code,找到script文件里config-center文件,修改config.txt:

...
service.vgroup-mapping.my_test_tx_group=default
...
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true&serverTimezone=UTC
store.db.user=root
store.db.password=123456
...

注意把vgroupMapping改成vgroup-mapping这是seata版本的问题。 新建nacos命名空间seata 运行写入配置中心:

sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t seata_namespace_id -u nacos -w nacos

打开script->server->db将里面mysql.sql写入seata_server数据库

将seata-server解压找到conf,按照官网配置file.conf和registry.conf,模式db,类型nacos, 命名空间同为seata_namespace_id。

启动seata-server服务。

然后配置seata-client服务,首先说明按照官网配置,不像其他技术栈那么简单明了,笔者尝试多种方法有不少坑要踩,另外还结合了官方给出的案例:GitHub - seata/seata-samples: seata-samples

找到里面的springcloud-nacos-seata文件,我们可以拿它的stock和order模块测试,在需要用到seata服务的不同库里我们都需要引入undo_log这个表,建表语句在官网和给出案例的readme.md里都能容易找到。 遗憾是按照其案例配置依然会报错,这大概和最新依赖配置不能匹配,所以按照idea工具的配置方式,配置了spring web/nacos/seata依赖。启动会报错,这是因为spring-cloud-starter-alibaba-seata这个包seata版本不是1.3.0或是缺少了seata-all这个包的依赖,最终引入依赖为:


<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>mysql-config</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

 在父pom里处理好了所以版本问题:

<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
 
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.27</version>
            </dependency>
 
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.1.1</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/io.seata/seata-all -->
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>1.3.0</version>
            </dependency>
 
            <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
                <version>2.2.10.RELEASE</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

引入的mysql-config模块配置:


@Configuration
public class config {
    /**
     * @param sqlSessionFactory SqlSessionFactory
     * @return SqlSessionTemplate
     */
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
 
    /**
     * 从配置文件获取属性构造datasource,注意前缀,这里用的是druid,根据自己情况配置,
     * 原生datasource前缀取"spring.datasource"
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }
 
    /**
     * 构造datasource代理对象,替换原来的datasource
     *
     * @param druidDataSource
     * @return
     */
    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
 
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSourceProxy);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // bean.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml"));
        bean.setMapperLocations(resolver.getResources("classpath*:mybatis/**/*-mapper.xml"));
 
        SqlSessionFactory factory = null;
        try {
            factory = bean.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return factory;
    }
 
    /**
     * MP 自带分页插件
     *
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor page = new PaginationInterceptor();
        page.setDialectType("mysql");
        return page;
    }
}

同时在启动类上把DataSourceAutoConfiguration默认配置排除:


@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = {"com.example"}, exclude = DataSourceAutoConfiguration.class)
public class OrderServiceApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    } 
}

application.yml:


server:
  port: 8000
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: 127.0.0.1:8848
        group: SEATA_GROUP
    alibaba:
      seata:
        # seata 服务分组,要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应
        tx-service-group: my_test_tx_group
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
      driverClassName: com.mysql.cj.jdbc.Driver
      username: root
      password: 123456
 
logging:
  level:
    io:
      seata: debug
 
seata:
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group : "SEATA_GROUP"
      namespace: "seata_namespace_id"
      username: "nacos"
      password: "nacos"
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: "SEATA_GROUP"
      namespace: "seata_namespace_id"
      username: "nacos"
      password: "nacos"
  application-id: ${spring.application.name}

StockService.java:

@Service
public class StockService {
 
    @Resource
    private StockDAO stockDAO;
 
    /**
     * 减库存
     *
     * @param commodityCode
     * @param count
     */
    @Transactional(rollbackFor = Exception.class)
    public void deduct(String commodityCode, int count) {
        QueryWrapper<Stock> wrapper = new QueryWrapper<>();
        wrapper.setEntity(new Stock().setCommodityCode(commodityCode));
        Stock stock = stockDAO.selectOne(wrapper);
        stock.setCount(stock.getCount() - count);
 
        stockDAO.updateById(stock);
        if (commodityCode.equals("product-2")) {
            throw new RuntimeException("异常:模拟业务异常:stock branch exception");
        }
    }
}

StockFeignClient.java:

@FeignClient(name = "stock-service")
public interface StockFeignClient {
 
    @GetMapping("stock/deduct")
    Boolean deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);
}

OrderService.java:

@Service
public class OrderService {
 
    @Resource
    private StockFeignClient stockFeignClient;
    @Resource
    private OrderDAO orderDAO;
 
//启用seata全局事务
    @GlobalTransactional
    @Transactional(rollbackFor = Exception.class)
    public void placeOrder(String userId, String commodityCode, Integer count) {
        BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
        Order order = new Order().setUserId(userId).setCommodityCode(commodityCode).setCount(count).setMoney(
                orderMoney);
        orderDAO.insert(order);
        stockFeignClient.deduct(commodityCode, count);
 
    }
 }

OrderController.java:


@RestController
@RequestMapping("order")
public class OrderController {
 
    @Resource
    private OrderService orderService;
 
    /**
     * 下单:插入订单表、扣减库存
     *
     * @return
     */
    @RequestMapping("/placeOrder/commit")
    public Boolean placeOrderCommit() {
 
        orderService.placeOrder("1", "product-1", 1);
        return true;
    }
 
    /**
     * 下单:插入订单表、扣减库存,模拟回滚
     *
     * @return
     */
    @RequestMapping("/placeOrder/rollback")
    public Boolean placeOrderRollback() {
        // product-2 扣库存时模拟了一个业务异常,
        orderService.placeOrder("1", "product-2", 1);
        return true;
    }
 
    @RequestMapping("/placeOrder")
    public Boolean placeOrder(String userId, String commodityCode, Integer count) {
        orderService.placeOrder(userId, commodityCode, count);
        return true;
    }
}

测试: 1.分布式事务成功,模拟正常下单、扣库存:

localhost:8000/order/placeOrder/commit

2.分布式事务失败,模拟下单成功、扣库存失败,最终同时回滚:

localhost:8000/order/placeOrder/rollback

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值