Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
更多的介绍可以参考官方文档:Seata快速入门
本篇主要是介绍Spring Cloud Alibaba + JPA 整合 Seata 的过程
cd /opt
tar -zxvf seata-server-1.3.0.tar.gz -C /usr/local
cd /usr/local/seata/
./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.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud-version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba-version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
该工程主要封装公共的VO,由于测试场景比较简单,目前只有一个类
@Data
@Builder
public class ReduceAmountDto {
private Long userId;
private Integer amount;
}
在订单-支付的关系中,支付服务相当于provider,订单服务相当于consumer。因此,这边我们先创建支付服务。
<dependencies>
<dependency>
<groupId>com.traingroupId>
<artifactId>common-serverartifactId>
<version>${project.parent.version}version>
dependency>
dependencies>
@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;
}
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
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("余额不足");
}
}
}
@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 "扣款成功";
}
}
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
<dependencies>
<dependency>
<groupId>com.traingroupId>
<artifactId>common-serverartifactId>
<version>${project.parent.version}version>
dependency>
dependencies>
@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;
}
public interface OrdersDao extends CrudRepository<Orders, Long> {
}
@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全局事务
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/save")
public String save(@RequestBody Orders order){
orderService.save(order);
return "操作结束";
}
}