概述
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
更多的介绍可以参考官方文档:Seata快速入门
本篇主要是介绍Spring Cloud Alibaba + JPA 整合 Seata 的过程
安装Seata
- 下载Seata,打开 https://github.com/seata/seata/releases,现在最新的是1.3.0,所以选择seata-server-1.3.0.tar.gz 进行下载,放到/opt
- 解压Seata到/usr/local目录下
cd /opt
tar -zxvf seata-server-1.3.0.tar.gz -C /usr/local
cd /usr/local/seata/
- 这边采用file单机模式(db模式相对麻烦一点,以后再另外介绍,从简单的先开始),直接运行命令启动即可
./bin/seata-server.sh
看到下面这个提示,说明启动成功,默认端口号8091
接下来介绍Spring Cloud Alibaba 接入Seata
业务场景
假设当前存在2个微服务:订单服务、支付服务。
- 用户下单时,订单服务需要调用支付服务进行扣款
- 当余额充足时下单成功,并全局提交事务
- 当余额不足时下单失败,并全局回滚事务
版本说明
框架 | 版本号 |
---|---|
Spring Boot | 2.1.13.RELEASE |
Spring Cloud | Greenwich.SR6 |
Spring Cloud Alibaba | 2.1.3.RELEASE |
MySQL | 8.0.11 |
创建父工程
创建父工程 hello-alibaba-seata,并引入依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-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>
</dependencies>
</dependencyManagement>
<dependencies>
<!--Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--Nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--MySQL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
创建公共服务 common-server
该工程主要封装公共的VO,由于测试场景比较简单,目前只有一个类
@Data
@Builder
public class ReduceAmountDto {
private Long userId;
private Integer amount;
}
创建支付服务 pay-order
在订单-支付的关系中,支付服务相当于provider,订单服务相当于consumer。因此,这边我们先创建支付服务。
- 引入 common-server 依赖
<dependencies>
<dependency>
<groupId>com.train</groupId>
<artifactId>common-server</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
- 创建账户 Entity
@Data
@Entity
@Table(name = "t_account")
public class AccountInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户id
*/
@Column(name = "user_id")
private Long userId;
/**
* 余额
*/
@Column(name = "amount")
private Integer amount;
}
- 创建Dao
public interface AccountDao extends CrudRepository<AccountInfo, Long> {
/**
* 根据userId获取账户信息
*/
@Query(value = "SELECT a FROM AccountInfo a where a.userId = :userId")
AccountInfo getByUserId(@Param("userId") Long userId);
/**
* 扣减账户余额
*/
@Modifying
@Query("UPDATE AccountInfo SET amount = amount - :amount WHERE userId = :userId")
Integer reduceAmount(@Param("userId") Long userId, @Param("amount") Integer amount);
}
- 创建 Service
@Service
public class PayServiceImpl implements PayService {
@Autowired
private AccountDao accountDao;
@Override
@Transactional(rollbackFor = Exception.class)
public void reduceAmount(Long userId, Integer reduceAmount) throws Exception {
checkAmount(userId, reduceAmount);
accountDao.reduceAmount(userId, reduceAmount);
}
private void checkAmount(Long userId, Integer reduceAmount) throws Exception {
AccountInfo accountInfo = accountDao.getByUserId(userId);
if (accountInfo == null){
throw new Exception("找不到账户信息");
}
if (accountInfo.getAmount() < reduceAmount){
throw new Exception("余额不足");
}
}
}
- 创建 controller
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private PayService payService;
@PostMapping("/reduceAmount")
public String reduceAmount(@RequestBody ReduceAmountDto input) throws Exception {
payService.reduceAmount(input.getUserId(), input.getAmount());
return "扣款成功";
}
}
- application.yml
server:
port: 8012
spring:
application:
name: pay-server
jpa:
database: mysql
datasource:
url: jdbc:mysql://192.168.25.129:3306/pay_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: test
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
cloud:
nacos:
discovery:
server-addr: 192.168.25.131:8848
management:
endpoints:
web:
exposure:
include: "*"
seata:
enabled: true
application-id: ${
spring.application.name}
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 192.168.25.131:8091
config:
type: file
file:
name: file.conf
registry:
type: file
file:
name: file.conf
enable-auto-data-source-proxy: true
注意:如果用的不是MySQL8,就要改下 driver-class-name
创建订单服务 order-server
- 引入 common-server 依赖
<dependencies>
<dependency>
<groupId>com.train</groupId>
<artifactId>common-server</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
- 创建订单 Entity
@Data
@Entity
@Table(name = "t_order")
public class Orders {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户id
*/
@Column(name = "user_id")
private Long userId;
/**
* 金额
*/
@Column(name = "amount")
private Integer amount;
}
- 创建订单 Dao
public interface OrdersDao extends CrudRepository<Orders, Long> {
}
- 创建 Service
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrdersDao orderDao;
@Autowired
private RestTemplate restTemplate;
@Override
@GlobalTransactional
public void save(Orders order) {
orderDao.save(order);
//扣款
String reduceUrl = "http://pay-server/pay/reduceAmount";
ReduceAmountDto input = ReduceAmountDto.builder().userId(order.getUserId()).amount(order.getAmount()).build();
String result = restTemplate.postForObject(reduceUrl, input, String.class);
log.info("调用pay-server结果:" + result);
}
}
@GlobalTransactional 表示开启Seata全局事务
- 创建 controller
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/save")
public String save(@RequestBody Orders order){
orderService.save(order);
return "操作结束";
}
}
测试
- 启动Nacos
- 启动Seata
- 启动pay-server
- 启动order-server
- 调用创建订单接口 http://localhost:8011/order/save ,构造相应的测试数据进行测试就行