springCloud(五)分布式事务seata

84 阅读2分钟

需求

实现在member模块中通过feign调用business模块,更新数据库的数据,如果成功都成功,如果失败都失败

seata 准备工作

下载安装seata

image.png

使用AT模式,所以要生成对应的sql

image.png

image.png

准备工作

member与business模块的curd

common模块

依赖

pom.xml

<dependency>  
    <groupId>com.alibaba.cloud</groupId>  
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>  
</dependency>

member模块

建立自己独立的库

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/springcloud_member?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai

配置seata

bootstrap.properties

新增

seata.tx-service-group=train-group  
# ????seata?????  
seata.service.vgroup-mapping.train-group=default  
# seata???????  
seata.service.grouplist.default=127.0.0.1:8091

business模块

建立自己独立的库

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/springcloud_business?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai

配置seata

bootstrap.properties

新增

seata.tx-service-group=train-group  
# ????seata?????  
seata.service.vgroup-mapping.train-group=default  
# seata???????  
seata.service.grouplist.default=127.0.0.1:8091

调用

member

feign

feign/BusinessFeign

@FeignClient("business")
public interface BusinessFeign {

    @PutMapping("/business/update")
     void update(User user);
}

service

service/UserService

@Service
public class UserService {
    private static final Logger LOG = LoggerFactory.getLogger(UserController.class);

    @Resource
    BusinessFeign businessFeign;
    @Resource
    private UserMapper userMapper;


    @GlobalTransactional
    public void testSeata(User user) throws Exception{
        LOG.info("seata全局事务ID: {}", RootContext.getXID());
        userMapper.updateByPrimaryKeySelective(user);
        businessFeign.update(user);
        if (1 == 1) {
            throw new Exception("测试异常");
        }
    }
}

controller

controller/UserController

@RestController
public class UserController {
    @Resource
    private UserService userService;

    @PutMapping("/test-seata")
    public String testSeata(@RequestBody User user) throws Exception {
        userService.testSeata(user);
        return "ok";
    }
}

异常处理

controller/ControllerExceptionHandler

@ControllerAdvice
public class ControllerExceptionHandler {

    private static final Logger LOG = LoggerFactory.getLogger(com.example.springCloud3.common.controller.ControllerExceptionHandler.class);

    /**
     * 所有异常统一处理
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Object exceptionHandler(Exception e) throws Exception {
        LOG.info("seata全局事务ID: {}", RootContext.getXID());
        // 如果是在一次全局事务里出异常了,就不要包装返回值,将异常抛给调用方,让调用方回滚事务
        if (StrUtil.isNotBlank(RootContext.getXID())) {
            throw e;
        }
        Map<String, Object> map = new HashMap<>();

        LOG.error("系统异常:", e);
        map.put("error",e.getMessage());
        return map;
    }
}

演示效果

调用前数据

image.png

调用失败情况

请求

image.png

数据库中的数据

member和business的数据库中的数据都都没有更改

image.png

事务回滚

image.png

调用成功情况

修改自己制造的异常

service/UserService

    @GlobalTransactional
    public void testSeata(User user) throws Exception{
        LOG.info("seata全局事务ID: {}", RootContext.getXID());
        userMapper.updateByPrimaryKeySelective(user);
        businessFeign.update(user);
        //自己制造异常
//        if (1 == 1) {
//            throw new Exception("测试异常");
//        }
    }

请求

image.png

数据库中的数据

member和business的数据库中的数据都成功更改

image.png

事务回滚

没有触发事务回滚

image.png

seate与sql存储

每次成功或失败后都会清除数据

service/UserService

增加延时器看效果Thread.sleep(10000);

    @GlobalTransactional
    public void testSeata(User user) throws Exception{
        LOG.info("seata全局事务ID: {}", RootContext.getXID());
        userMapper.updateByPrimaryKeySelective(user);
        businessFeign.update(user);
        //自己制造异常
        Thread.sleep(10000);
//        if (1 == 1) {
//            throw new Exception("测试异常");
//        }
    }

2.gif