阿里分布式事务框架Seata简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
中文官方站点:Seata
seata github开源地址:Seata · GitHub
分布式事务简介 本地事务
在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。
数据库事务的四大特性:ACID
A(Atomic):原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况。
C(Consistency):一致性,在事务执行前后,数据库的一致性约束没有被破坏。比如:张三向李四转100 元,转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出 100 元,李四账户没有增加100 元这就出现了数 据错误,就没有达到一致性。
I(Isolation):隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务的运行过程的中间状态。通过配置事务隔离级别可以比避免脏读、重复读问题。
D(Durability):持久性,事务完成之后,该事务对数据的更改会持久到数据库,且不会被回滚。数据库事务在实现时会将一次事务的所有操作全部纳入到一个不可分割的执行单元,该执行单元的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。
分布式事务
随着互联网的快速发展,软件系统由原来的单体应用转变为分布式应用分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。
如何使用分布式事务
首先在原本得用户项目
在mapper包里添加转账
package com.cxy.account.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxy.common.entity.Account;
import java.util.Map;
public interface AccountMapper extends BaseMapper<Account> {
/**
* 账户扣钱
*/
void decrease(Map map);
/**
* 账户加钱
*/
void increase(Map map);
}
?sql语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cxy.account.mapper.AccountMapper">
<update id="decrease" parameterType="Map">
update account set balance = balance - #{amount} where card_no = #{cardNo}
</update>
<update id="increase" parameterType="Map">
update account set balance = balance + #{amount} where card_no = #{cardNo}
</update>
</mapper>
接口实现
package com.cxy.account.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cxy.common.entity.Account;
public interface AccountService extends IService<Account> {
/**
* 转账
* @param myCardNo
* @param otherCardNo
* @param amount
*/
void transfer(String myCardNo, String otherCardNo, Double amount);
}
继承使用了@Transactional注解开启事务
package com.cxy.account.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cxy.account.mapper.AccountMapper;
import com.cxy.account.service.AccountService;
import com.cxy.common.entity.Account;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
@Resource
private AccountMapper accountMapper;
/**
* 转账
* @param myCardNo
* @param otherCardNo
* @param amount
*/
@Transactional
@Override
public void transfer(String myCardNo, String otherCardNo, Double amount) {
System.out.println("================"+myCardNo+"===="+amount);
//账户转出
Map<String,Object> decreaseMap=new HashMap<>();
decreaseMap.put("cardNo",myCardNo);
decreaseMap.put("amount",amount);
accountMapper.decrease(decreaseMap);
//账户转入
Map<String,Object> increaseMap=new HashMap<>();
increaseMap.put("cardNo",otherCardNo);
increaseMap.put("amount",amount);
this.baseMapper.increase(increaseMap);
}
}
在controller里进行实现
@PostMapping("/transfer")
public Result transfer(String myCardNo, String otherCardNo, Double amount){
accountService.transfer(myCardNo,otherCardNo,amount);
return Result.ok();
}
?建seata-service子项目
添加依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>alibaba</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-service</artifactId>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>common-servic</artifactId>
<version>1.0-SNAPSHOT</version>
<!--如果引用冲突并且不需要使用或者自带了,可以用排除不需要的依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--服务的注册于发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--远程调用OPenFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡LoadBalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!--服务的配置-->
<!--<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>-->
<!--解决2020/2021版本无法加载bootstrap.yaml文件bug-->
<!-- <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>-->
</dependencies>
</project>
?配置pom文件
server:
port: 80
servlet:
context-path: /
spring:
cloud:
nacos:
discovery:
server-addr: 101.42.157.178:8848
application:
name: seata-service
在feign包里建AccountFeignService类,这里添加了一个@FeignClient("account-service")作用是调用其他服务,如果没有会报错,疯狂的报
package com.cxy.seata.feign;
import com.cxy.common.response.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("account-service")
public interface AccountFeignService {
@PostMapping("/transfer")
Result transfer(@RequestParam(value = "myCardNo") String myCardNo,
@RequestParam(value = "otherCardNo")String otherCardNo,
@RequestParam(value = "amount")Double amount);
}
在controller包里建WebController类
package com.cxy.seata.controller;
import com.cxy.common.entity.TransactionRecord;
import com.cxy.common.response.Result;
import com.cxy.seata.feign.AccountFeignService;
import com.cxy.seata.feign.RecordFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WebController {
@Autowired
private AccountFeignService accountFeignService;
@Autowired
private RecordFeignService recordFeignService;
/**
* 下单 1,创建订单 2,账户扣钱
* @param
* @return
*/
@PostMapping("/tran")
public Result tran(@RequestBody TransactionRecord record, String myCardNo, String otherCardNo, Double amount){
//实现转账交易
accountFeignService.transfer(myCardNo,otherCardNo,amount); // 账户扣钱
//生成转账记录
recordFeignService.addRecord(record); // 创建订单
return Result.ok();
}
}
这样就搭建好了
血一样的教训/(ㄒoㄒ)/~~
运行半天一种在nacos服务的错,那肯定是忘记加这里的注解@RequestParam,每个都要,为什么呢,因为分布式事务没有参数
?还有这款的·对象要加上@RequestBody,一样的道理
?还有一个就是我们需要排除数据库就需要在启动类的关键注解上加入
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
别问为什么加上就可以了,如果你不加就会报一直扫描不到数据库,你可能半天都找不出了什么原因,因为他是自动会扫描url,name啊什么的,这个错会在很多时候报,记住记住
|