目录
引言
一、事务管理基础
1. 什么是事务?
2. 事务管理的方式
二、声明式事务管理:@Transactional
1. 基础使用
2. 事务传播行为
3. 事务隔离级别
三、分布式事务入门:Seata 的基本使用
1. 什么是分布式事务?
2. Seata 简介
3. 集成 Seata 到 Spring Boot 应用
步骤 1:添加依赖
步骤 2:配置 Seata
步骤 3:配置数据源
步骤 4:编写业务代码
4. 测试分布式事务
在现代企业级应用开发中,事务管理是确保数据一致性和完整性的重要环节。无论是单体应用还是微服务架构,事务管理都扮演着不可或缺的角色。本篇文章将从声明式事务管理入手,结合Spring框架的@Transactional
注解,深入探讨事务传播和隔离级别的配置与使用。随后,我们将扩展到分布式事务领域,介绍Seata的基本使用和配置方法。
事务是数据库操作的一个逻辑单元,必须满足以下四个特性(ACID):
在Java应用中,事务管理主要有两种方式:
@Transactional
)来声明事务边界,由容器自动管理。由于声明式事务管理简洁易用且符合“关注点分离”原则,本文将重点介绍其使用方法。
@Transactional
是Spring框架提供的一个注解,用于声明式地管理事务。它可以应用于类或方法级别。
示例代码:
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public void addUserAndLog(String username) {
User user = new User();
user.setUsername(username);
userMapper.insert(user);
// 记录日志
Log log = new Log();
log.setUsername(username);
logMapper.insert(log);
}
}
@Transactional
:默认情况下,该注解会开启一个事务,并在方法执行完毕后提交事务。如果方法抛出异常,则回滚事务。
Spring支持多种事务传播行为,用于控制方法之间的事务关联方式。常见的传播行为包括:
示例代码:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private PaymentMapper paymentMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order, Payment payment) {
orderMapper.insert(order);
// 子方法自动加入同一事务
pay(order.getId(), payment);
}
@Transactional(propagation = Propagation.REQUIRED)
private void pay(Long orderId, Payment payment) {
payment.setOrderId(orderId);
paymentMapper.insert(payment);
}
}
Propagation.REQUIRED
:确保两个方法在一个事务中执行。
事务隔离级别决定了不同事务之间可见性的程度。Spring支持以下几种隔离级别:
REPEATABLE_READ
)。示例代码:
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney(Long fromId, Long toId, Double amount) {
Account fromAccount = accountMapper.selectById(fromId);
Account toAccount = accountMapper.selectById(toId);
if (fromAccount.getBalance() >= amount) {
fromAccount.setBalance(fromAccount.getBalance() - amount);
accountMapper.updateById(fromAccount);
toAccount.setBalance(toAccount.getBalance() + amount);
accountMapper.updateById(toAccount);
}
}
}
Isolation.READ_COMMITTED
:防止脏读,但允许不可重复读和幻读。
在微服务架构中,一个业务操作可能需要多个服务协同完成。例如,在电商系统中,下单操作可能需要调用订单服务、库存服务和支付服务。这种跨服务的操作被称为分布式事务。
传统的JTA(Java Transaction API)在分布式环境下表现不佳,因此出现了新的解决方案——Seata。
Seata 是一个开源的分布式事务解决方案,支持多种分布式事务模式:
本文将重点介绍 AT 模式的使用。
在 pom.xml
中添加 Seata 和相关的依赖:
io.seata
seata-spring-boot-starter
1.7.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-jdbc
在 application.properties
文件中添加以下配置:
# Seata 配置
seata.enabled=true
seata.application-id=order-service
seata.tx-service-group=my_tx_group
seata.service.vgroup-mapping.my_tx_group=1
seata.service.node=192.168.1.100:8091
确保你的数据源配置支持分布式事务。例如:
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 配置连接池
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
在需要进行分布式事务的方法上添加 @GlobalTransactional
注解:
import io.seata.spring.annotation.GlobalTransactional;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@GlobalTransactional(timeout = 30000)
public void createOrder(Order order, Payment payment) {
// 创建订单
orderMapper.insert(order);
// 扣减库存
inventoryService.decreaseInventory(order.getItemId(), order.getNum());
// 支付
paymentService.pay(order.getUserId(), payment.getAmount());
}
}
@GlobalTransactional
:声明这是一个全局事务。timeout
:设置事务超时时间(单位:毫秒)。为了验证分布式事务的有效性,可以设计一个测试场景:
如果任何一个步骤失败,整个事务应回滚。
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testCreateOrder() {
Order order = new Order();
order.setItemId(1L);
order.setNum(2);
order.setUserId(1L);
Payment payment = new Payment();
payment.setAmount(100.0);
orderService.createOrder(order, payment);
}
}