【Seata】SpringBoot集成Seata1.6-AT模式

目录

开始语

😎项目目录结构如下

🖋️数据库执行脚本

🖋️pom.xml文件

🖋️项目所需配置文件application.yml如下

🖋️nacos中配置

🖋️common模块 

🖋️服务调用Orders 请求 Inventory代码片段

😎源码地址 

结束语


开始语

一位普通的程序员,慢慢在努力变强!

没有安装seata的同学点击此处前往👈

没有安装nacos的同学点击此处前往👈

😎项目目录结构如下

🖋️数据库执行脚本

-- 数据库
CREATE DATABASE spring-boot-seata DEFAULT CHARACTER SET utf8mb4;

-- `spring-boot-seata`.inventory definition
CREATE TABLE `inventory` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`buss_name` varchar(100) DEFAULT NULL COMMENT '库存名称',
`number` bigint(20) DEFAULT NULL COMMENT '库存量',
`create_user` varchar(50) DEFAULT ' ' COMMENT '创建人',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_user` varchar(50) DEFAULT ' ' COMMENT '修改人',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`deleted` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效(0.不可用,1.可用)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';

-- `spring-boot-seata`.orders definition

CREATE TABLE `orders` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_name` varchar(100) DEFAULT NULL COMMENT '订单名称',
`amount` decimal(12,4) DEFAULT NULL COMMENT '订单金额',
`number` bigint(20) DEFAULT NULL COMMENT '订单数',
`create_user` varchar(50) DEFAULT ' ' COMMENT '创建人',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_user` varchar(50) DEFAULT ' ' COMMENT '修改人',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`deleted` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效(0.不可用,1.可用)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log,如果你是多个数据库,那么在对应的库中需要添加这张表
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

🖋️pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <packaging>pom</packaging>

    <groupId>com.net</groupId>
    <artifactId>springboot-seata</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-seata</name>
    <description>分布式事务的学习</description>

    <modules>
        <module>orders</module>
        <module>inventory</module>
        <module>common</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>11</java.version>
        <springboot.version>2.3.12.RELEASE</springboot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-config-spring-boot-starter</artifactId>
            <version>0.2.7</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.6.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${springboot.version}</version>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>

            <!--maven编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <!-- maven 打包时跳过测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>

        <resources>
            <!--编译||打包排除和加载设置 install-->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <!-- 先排除所有环境相关的配置文件 -->
                <excludes>
                    <!--<exclude>application*.yml</exclude>-->
                </excludes>
            </resource>
            <resource>
                <!--打包编译项目默认编译加载的文件,例如一些txt,xml...等文件-->
                <directory>src/main/resources</directory>
                <!--提供@配置@在yml文件中支持读取xml文件配置-->
                <filtering>true</filtering>
                <includes>
                    <include>banner.txt</include>
                    <include>bootstrap.yml</include>
                    <include>application.properties</include>
                    <include>application.yml</include>
                </includes>
            </resource>
        </resources>
    </build>



    <!--环境切换-->
    <profiles>
        <profile>
            <!--开发环境-->
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <project.active>dev</project.active>
                <nacos.addr>localhost:8848</nacos.addr>
                <nacos.service>localhost:8091</nacos.service>
            </properties>
        </profile>
        <profile>
            <!--测试环境-->
            <id>test</id>
            <properties>
                <project.active>test</project.active>
            </properties>
        </profile>
        <profile>
            <!--验收环境,仅次于prod生产环境-->
            <id>beta</id>
            <properties>
                <project.active>beta</project.active>
            </properties>
        </profile>
    </profiles>

</project>

🖋️项目所需配置文件application.yml如下

温馨提示:inventory举例(orders同理),后面我会发出demo的git地址

server:
  port: 8091

spring:
  application:
    name: @project.name@
  profiles:
    # 如果不生效请刷新maven依赖
    active: @project.active@

# start------------------------------这块配置应该放入nacos中,这边方便演示,所以就没有放入nacos中
seata:
  config:
    type: nacos
    nacos:
      server-addr: @nacos.addr@
      namespace: seata-server
      group: SEATA_GROUP
      username: nacos
      password: Getring0817#.
  registry:
    type: nacos
    nacos:
      server-addr: @nacos.addr@
      namespace: seata-server
      application: seata-server
      group: SEATA_GROUP
      username: nacos
      password: Getring0817#.
  service:
    vgroup-mapping:
      my_test_tx_group: default
    disable-global-transaction: false
    grouplist:
      default: @nacos.service@
# end------------------------------这块配置应该放入nacos中,这边方便演示,所以就没有放入nacos中

# nacos服务配置
nacos:
  config:
    server-addr: @nacos.addr@
    namespace: ${spring.profiles.active}
    username: nacos
    password: Getring0817#.
    type: yaml
    bootstrap:
      enable: true
      log-enable: true
    auto-refresh: true
    enable-remote-sync-config: true
    data-ids: ${spring.application.name}-server.yaml,${spring.application.name}-db.yaml

logging:
  level:
    # 解决注册中心跳机制的日志问题
    com.alibaba.nacos.client.config.impl: warn

🖋️nacos中配置

 

# 放置inventory-db.yaml文件配置中
spring:
  #----------------------------mysql服务配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-boot-seata?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
    username: root
    password: root_123
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 10
      min-idle: 10
      maxActive: 200
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      connectionErrorRetryAttempts: 3
      breakAfterAcquireFailure: true
      timeBetweenConnectErrorMillis: 300000
      asyncInit: true
      remove-abandoned: false
      remove-abandoned-timeout: 1800
      transaction-query-timeout: 6000
      filters: stat,wall,log4j2
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      web-stat-filter:
        enabled: true
        url-pattern: "/*"
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      stat-view-servlet:
        url-pattern: "/druid/*"
        allow:
        deny:
        reset-enable: false
        login-username: admin
        login-password: admin

mybatis-plus:
  global-config:
    db-config:
      # 逻辑已删除值(默认为 1)
      logic-delete-value: 1
      # 逻辑未删除值(默认为 0)
      logic-not-delete-value: 0
  mapper-locations: classpath*:**/mapper/xml/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 驼峰,_映射 app_name = appName
    map-underscore-to-camel-case: true

🖋️common模块 

package com.net.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }

}
package com.net.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * spring上下文帮助类
 *
 * @author tianyu.Ge
 * @date 2023/3/3 9:19
 */
@Component
public class SpringApplicationContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringApplicationContextUtil.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static <T> T getBean(String beanName) {
        return (T) applicationContext.getBean(beanName);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    public static boolean containsBean(String beanName) {
        return applicationContext.containsBean(beanName);
    }

}

package com.net.utils;

import io.seata.core.context.RootContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

/**
 * Htttp工具类
 *
 * @author tianyu.Ge
 * @date 2023/3/3 9:17
 */
public class HTTPUtil {

    public static final RestTemplate HTTP_UTIL() {
        RestTemplate restTemplate = SpringApplicationContextUtil.getBean(RestTemplate.class);
        return restTemplate;
    }

    /**
     * seata需要分布式事务专属方法
     *
     * @param url    请求路径
     * @param resultClass 返回类型
     * @return T
     * @author tianyu.Ge
     * @date 2023/3/3 9:29
     */
    public static <T> T get(String url, Class<T> resultClass) {
        HttpHeaders headers = new HttpHeaders();
        // 添加一些head设置,比如token等
        HttpEntity httpEntity = new HttpEntity(headers);
        ResponseEntity<T> exchange = HTTP_UTIL().exchange(url, HttpMethod.GET, httpEntity, resultClass);
        return exchange.getBody();
    }

    /**
     * seata需要分布式事务专属方法
     *
     * @param url    请求路径
     * @param params 请求参数
     * @param resultClass 返回类型
     * @return T
     * @author tianyu.Ge
     * @date 2023/3/3 9:29
     */
    public static <T> T seataGetFrom(String url, Map<String, Object> params, Class<T> resultClass) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.add(RootContext.KEY_XID, RootContext.getXID());
        StringBuffer stringBuffer = new StringBuffer(url + "?");
        if (params != null && params.size() > 0) {
            params.forEach((k, v) -> {
                stringBuffer.append(String.format("&%s=%s", k, v));
            });
        }
        HttpEntity<String> httpEntity = new HttpEntity(headers);
        ResponseEntity<T> exchange = HTTP_UTIL().exchange(stringBuffer.toString(), HttpMethod.GET, httpEntity, resultClass);
        return exchange.getBody();
    }

}

🖋️服务调用Orders 请求 Inventory代码片段

-------------------Orders服务service模块代码-------------------

package com.net.module.service.impl;

import com.net.module.entity.Orders;
import com.net.module.mapper.OrdersMapper;
import com.net.module.service.IOrdersService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.net.utils.HTTPUtil;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;

import java.util.HashMap;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author 猿仁
 * @since 2023-03-02
 */
@Service
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersService {

    /**
     * 下单
     * 模拟事务场景,实际业务和表设计并不是如此
     *
     * @param orders
     * @param inventoryId 库存id
     * @return
     * @author tianyu.Ge
     * @date 2023/3/2 16:50
     */
    @GlobalTransactional
    @Override
    public boolean placeAnOrder(Long inventoryId, Orders orders) {
        System.out.println("account XID " + RootContext.getXID());
        if (orders.getNumber() == null || orders.getNumber() != 1) {
            throw new RuntimeException("订单数量只能选1个!");
        }
        // 新增订单
        boolean save = save(orders);
        // 调用库存-1接口(将XID传递给下一个接口,或者其他服务,让对应的服务读取Header请求头XID被全局事务进行管理)
        Boolean boyd = HTTPUtil.seataGetFrom("http://127.0.0.1:8091/api/inventory/inventorySubOne/" + inventoryId, new HashMap<>() {{
            put("message", "请检查是否异常!");
        }}, Boolean.class);
        // 场景1,库存失败
        if (!boyd) {
            throw new RuntimeException("库存扣除失败,请查看详情!");
        } else {
            if (boyd) {
                // 场景2,库存成功,业务处理异常...
                throw new RuntimeException("订单业务处理繁忙,请稍后再试!");
            }
        }
        return save;
    }
}
-------------------Inventory服务service模块代码-------------------

package com.net.module.service.impl;

import com.net.module.entity.Inventory;
import com.net.module.mapper.InventoryMapper;
import com.net.module.service.IInventoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * <p>
 * 库存表 服务实现类
 * </p>
 *
 * @author 猿仁
 * @since 2023-03-02
 */
@Slf4j
@Service
public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements IInventoryService {

    @Autowired
    private InventoryMapper inventoryMapper;

    /**
     * 库存减一
     *
     * @param id 库存id
     * @return boolean
     * @author tianyu.Ge
     * @date 2023/3/2 17:00
     */
    @Transactional
    @Override
    public boolean inventorySubOne(Long id) {
        Inventory one = lambdaQuery().eq(Inventory::getId, id).one();
        if (null == one) {
            log.error("没有库存id为:" + id + "的数据!");
            return false;
        }
        Long number = one.getNumber();
        if (number == 0) {
            log.error("当前库存为0,请检查订单对应的商品正确性,期望下架,支付金额返回支付账户!");
            return false;
        }
        boolean update = lambdaUpdate().set(Inventory::getNumber, number - 1).eq(Inventory::getId, id).update();
        return update;
    }

    /**
     * 根据库存名称查询库存信息
     *
     * @param bussName
     * @return com.net.module.entity.Inventory
     * @author tianyu.Ge
     * @date 2023/3/3 8:33
     */
    @Override
    public Inventory getByBussName(String bussName) {
        return inventoryMapper.getByBussName(bussName);
    }
}

😎源码地址 

>>>>>>>>>>>>>>>代码仓库地址👈https://gitee.com/gtring/springboot-seata.git

结束语

本章节完成了,各位正在努力的程序员们,如果你们觉得本文章对您有用的话,你学到了一些东西,希望猿友们点个赞+关注,支持一下猿仁!
持续更新中…

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Seata is an open-source distributed transaction solution that provides a high-performance and easy-to-use distributed transaction coordination service for Spring Boot applications. It supports distributed transaction management across multiple databases, message queues, and other resources, ensuring the consistency and reliability of data operations. To integrate Seata into a Spring Boot application, you need to follow the following steps: 1. Add Seata dependencies to your pom.xml file: ``` <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.4.1</version> </dependency> ``` 2. Configure Seata in your application.yml file: ``` spring: cloud: alibaba: seata: tx-service-group: my_test_tx_group enable-auto-data-source-proxy: true application-id: my_test_app tx-log: dir: {your_log_dir} name: {your_log_name} max-size: {your_log_max_size} max-history: {your_log_max_history} ``` 3. Enable Seata in your Spring Boot application: ``` @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @EnableAutoDataSourceProxy @EnableHystrix @EnableAspectJAutoProxy(exposeProxy = true) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean public GlobalTransactionScanner globalTransactionScanner() { return new GlobalTransactionScanner("my_test_tx_group", "my_test_app"); } } ``` 4. Use Seata in your business logic: ``` @RestController public class DemoController { @Autowired private DemoService demoService; @RequestMapping("/test") public String test() { demoService.doBusiness(); return "OK"; } } @Service public class DemoService { @Autowired private DemoMapper demoMapper; @GlobalTransactional public void doBusiness() { // business logic using demoMapper } } ``` In the above example, the `@GlobalTransactional` annotation is used to wrap the business logic in a distributed transaction. This ensures that all the database operations performed by the `demoMapper` are atomic and consistent, even if they involve multiple resources. Overall, Seata is a powerful and flexible solution for managing distributed transactions in Spring Boot applications. By following the above steps, you can easily integrate Seata into your project and ensure the reliability and consistency of your data operations.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿仁

多一分支持,多一分动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值