Spring Boot: Service 层与事务管理
最后更新:2026-08-26
事务是数据一致性的守护者——下单扣库存必须原子完成,要么全成功,要么全回滚。
1. 你将学到
- Service 层设计模式:接口 + 实现,
@Service注解与依赖注入 @Transactional传播行为(REQUIRED、REQUIRES_NEW、NESTED)- 事务回滚规则:
rollbackFor/noRollbackFor配置 - 只读事务
@Transactional(readOnly = true)性能优化 - Alice 实现下单扣库存的原子事务操作
2. 一个业务开发者的真实故事
(1) 痛点:下单扣库存不一致
Alice 发现 OrderFlow 有一个严重 bug:用户下单成功后,商品库存没有扣减。原因是创建订单和扣减库存是两个独立操作,创建订单成功但扣库存失败时,数据出现不一致。Charlie 报告说已有 5 thousand 个超卖订单,公司损失超过 10 thousand USD。
(2) 声明式事务的解法
Spring 的 @Transactional 注解让事务管理变成一行代码:
JAVA
@Transactional
public Order createOrder(CreateOrderRequest request) {
Product product = productRepository.findById(request.productId()).orElseThrow();
product.deductStock(request.quantity()); // Deduct stock
Order order = new Order(product, request.quantity());
return orderRepository.save(order); // Create order
}
扣库存失败?整个事务自动回滚,订单也不会创建。
(3) 收益
Alice 加上 @Transactional 后,下单扣库存保证原子性,超卖问题彻底消除。Charlie 的投诉量降为零。
3. Service 层设计模式
(1) 分层架构
graph TD
A["Controller<br/>Request/Response"] --> B["Service<br/>Business Logic<br/>@Transactional"]
B --> C["Repository<br/>Data Access"]
C --> D["Database"]
| 层 | 职责 | 注解 | 命名约定 |
|---|---|---|---|
| Controller | 接收请求、参数校验、返回响应 | @RestController |
*Controller |
| Service | 业务逻辑、事务编排 | @Service |
*Service / *ServiceImpl |
| Repository | 数据访问 | @Repository |
*Repository |
▶ 示例: Service 接口与实现
JAVA
// OrderService.java (interface)
package com.orderflow.service;
import com.orderflow.model.Order;
public interface OrderService {
Order createOrder(Long productId, Integer quantity);
Order cancelOrder(Long orderId);
Order getOrder(Long orderId);
}
// OrderServiceImpl.java (implementation)
package com.orderflow.service;
import com.orderflow.model.*;
import com.orderflow.repository.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
public OrderServiceImpl(OrderRepository orderRepository,
ProductRepository productRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
@Override
@Transactional
public Order createOrder(Long productId, Integer quantity) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
product.deductStock(quantity);
Order order = new Order();
order.addItem(product, quantity);
return orderRepository.save(order);
}
@Override
@Transactional(readOnly = true)
public Order getOrder(Long orderId) {
return orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("Order not found"));
}
}
输出:
TEXT
📖 仅展示
// 执行成功
4. @Transactional 传播行为
(1) 七种传播行为
| 传播行为 | 含义 | 典型场景 |
|---|---|---|
REQUIRED(默认) |
有事务加入,没有新建 | 绝大多数业务方法 |
REQUIRES_NEW |
总是新建事务,挂起当前事务 | 日志记录(不受外层回滚影响) |
NESTED |
嵌套事务(savepoint),外层回滚则内层也回滚 | 子操作允许独立回滚 |
SUPPORTS |
有事务加入,没有非事务执行 | 查询方法 |
NOT_SUPPORTED |
非事务执行,挂起当前事务 | 不需要事务的操作 |
MANDATORY |
必须在事务中调用,否则抛异常 | 强制要求事务的方法 |
NEVER |
必须非事务调用,否则抛异常 | 不允许事务的操作 |
sequenceDiagram
participant C as Controller
participant S1 as createOrder (REQUIRED)
participant S2 as recordAudit (REQUIRES_NEW)
participant S3 as deductStock (REQUIRED)
C->>S1: Begin Tx1
S1->>S3: Join Tx1
S3-->>S1: OK
S1->>S2: Suspend Tx1, Begin Tx2
S2-->>S1: Commit Tx2, Resume Tx1
alt Tx1 rollback
S1-->>C: Rollback Tx1 (Tx2 not affected)
end
▶ 示例: REQUIRES_NEW 保证审计日志
JAVA
@Service
public class AuditService {
private final AuditLogRepository auditLogRepository;
public AuditService(AuditLogRepository auditLogRepository) {
this.auditLogRepository = auditLogRepository;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAudit(String action, String detail) {
AuditLog log = new AuditLog(action, detail, Instant.now());
auditLogRepository.save(log);
}
}
输出:
TEXT
📖 仅展示
// 执行成功
📌 重点:
REQUIRES_NEW 让审计日志在独立事务中提交,即使外层事务回滚,日志也不会丢失。
5. 事务回滚规则
(1) 默认回滚行为
Spring 事务默认只对 RuntimeException 和 Error 回滚,不对受检异常回滚。
▶ 示例: rollbackFor 配置
JAVA
@Service
public class OrderServiceImpl implements OrderService {
@Transactional(rollbackFor = Exception.class)
public Order createOrder(Long productId, Integer quantity) throws Exception {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
product.deductStock(quantity);
Order order = new Order();
order.addItem(product, quantity);
return orderRepository.save(order);
}
}
输出:
TEXT
📖 仅展示
// 执行成功
| 配置 | 含义 | 适用场景 |
|---|---|---|
rollbackFor = Exception.class |
所有异常都回滚 | 默认推荐配置 |
rollbackFor = BusinessException.class |
仅自定义异常回滚 | 精细控制 |
noRollbackFor = ValidationException.class |
指定异常不回滚 | 某些异常不触发回滚 |
6. 只读事务优化
▶ 示例: 只读事务
JAVA
@Service
public class OrderQueryService {
private final OrderRepository orderRepository;
public OrderQueryService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional(readOnly = true)
public Order getOrder(Long id) {
return orderRepository.findById(id).orElseThrow();
}
@Transactional(readOnly = true)
public Page<Order> listOrders(String status, Pageable pageable) {
return orderRepository.findByStatus(status, pageable);
}
}
输出:
TEXT
📖 仅展示
// 执行成功
| 维度 | 读写事务 | 只读事务 |
|---|---|---|
| 脏检查 | 启用(逐字段对比) | 跳过 |
| 性能 | 较慢 | 快 20-30% |
| 写操作 | 允许 | 禁止(写会抛异常) |
| 适用 | CUD 操作 | 查询操作 |
💡 提示: 所有查询方法都应加
@Transactional(readOnly = true),这是零成本的性能优化。
7. 下单扣库存原子事务
▶ 示例: 完整下单流程
JAVA
@Service
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
private final AuditService auditService;
public OrderServiceImpl(OrderRepository orderRepository,
ProductRepository productRepository,
AuditService auditService) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
this.auditService = auditService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Order createOrder(Long productId, Integer quantity) {
// 1. Find product
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found: " + productId));
// 2. Check stock
if (product.getStock() < quantity) {
throw new RuntimeException("Insufficient stock: available="
+ product.getStock() + ", requested=" + quantity);
}
// 3. Deduct stock (in same transaction)
product.deductStock(quantity);
// 4. Create order with order item
Order order = new Order();
order.addItem(product, quantity);
Order saved = orderRepository.save(order);
// 5. Audit log (in separate transaction via REQUIRES_NEW)
auditService.logAudit("CREATE_ORDER",
"Order " + saved.getId() + " created for product " + productId);
return saved;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Order cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("Order not found: " + orderId));
if (!"PENDING".equals(order.getStatus())) {
throw new RuntimeException("Cannot cancel order with status: " + order.getStatus());
}
// Restore stock for each item
for (OrderItem item : order.getItems()) {
item.getProduct().addStock(item.getQuantity());
}
order.setStatus("CANCELLED");
auditService.logAudit("CANCEL_ORDER", "Order " + orderId + " cancelled");
return order;
}
}
输出:
TEXT
📖 仅展示
// 执行成功
8. 综合示例:OrderFlow Service 层完整实现
JAVA
// ProductService.java
package com.orderflow.service;
import com.orderflow.model.Product;
import com.orderflow.repository.ProductRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Transactional(readOnly = true)
public Product getProduct(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Product not found"));
}
@Transactional(readOnly = true)
public Page<Product> listProducts(Pageable pageable) {
return productRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public List<Product> searchProducts(String keyword) {
return productRepository.findByNameContaining(keyword);
}
@Transactional
public Product createProduct(String name, BigDecimal price, Integer stock) {
return productRepository.save(new Product(name, price, stock));
}
@Transactional
public Product updateProduct(Long id, String name, BigDecimal price, Integer stock) {
Product product = getProduct(id);
product.setName(name);
product.setPrice(price);
product.setStock(stock);
return product;
}
}
// OrderService.java
package com.orderflow.service;
import com.orderflow.model.Order;
import com.orderflow.model.Product;
import com.orderflow.repository.OrderRepository;
import com.orderflow.repository.ProductRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
public OrderService(OrderRepository orderRepository,
ProductRepository productRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
@Transactional(rollbackFor = Exception.class)
public Order createOrder(Long productId, Integer quantity) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
if (product.getStock() < quantity) {
throw new RuntimeException("Insufficient stock");
}
product.deductStock(quantity);
Order order = new Order();
order.addItem(product, quantity);
return orderRepository.save(order);
}
@Transactional(rollbackFor = Exception.class)
public Order cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("Order not found"));
order.getItems().forEach(item -> item.getProduct().addStock(item.getQuantity()));
order.setStatus("CANCELLED");
return order;
}
@Transactional(readOnly = true)
public Order getOrder(Long orderId) {
return orderRepository.findByIdWithItems(orderId)
.orElseThrow(() -> new RuntimeException("Order not found"));
}
}
❓ 常见问题
Q @Transactional 加在接口还是实现类上?
A 推荐加在实现类的方法上。加在接口上时,基于 CGLIB 的代理无法拦截接口注解。Spring 官方也推荐加在具体类上。
Q 同一个类中方法互调 @Transactional 会生效吗?
A 不会。Spring 事务基于代理,同类方法互调不走代理,事务不生效。解决方案:1)拆分到不同 Service;2)注入自身代理
@Lazy private OrderService self;。Q readOnly=true 时执行写操作会怎样?
A 取决于数据库和连接池配置。Hibernate 会抛异常,某些数据库驱动忽略。不要在只读事务中执行写操作。
Q NESTED 和 REQUIRES_NEW 有什么区别?
A NESTED 是嵌套事务(savepoint),外层回滚时内层也回滚。REQUIRES_NEW 是独立事务,外层回滚不影响内层。需要独立提交用 REQUIRES_NEW,需要部分回滚用 NESTED。
Q 事务失效的常见原因有哪些?
A 1)同类方法互调;2)方法非 public;3)异常被 catch 吞掉;4)默认只回滚 RuntimeException,受检异常不回滚。
Q 如何验证事务是否生效?
A 1)开启
spring.jpa.show-sql=true 观察 SQL 执行;2)设置 logging.level.org.springframework.transaction=DEBUG 查看事务日志;3)故意抛异常验证回滚。📖 小节
- Service 层封装业务逻辑,Controller 调 Service,Service 调 Repository
@Transactional默认 REQUIRED 传播,RuntimeException 自动回滚REQUIRES_NEW用于独立事务(如审计日志),NESTED用于嵌套事务rollbackFor = Exception.class确保所有异常都回滚@Transactional(readOnly = true)对查询方法是零成本优化- 同类方法互调事务不生效,是 Spring 代理机制的限制
📝 作业
-
基础题(难度⭐):为 OrderFlow 的 ProductService 和 OrderService 添加
@Transactional注解,验证下单扣库存的原子性——在扣库存后故意抛异常,确认订单也不会创建。 -
进阶题(难度⭐⭐):创建 AuditService 使用
REQUIRES_NEW传播行为,确保即使订单创建事务回滚,审计日志也不丢失。 -
挑战题(难度⭐⭐⭐):模拟并发下单场景(两个请求同时购买库存仅剩 1 件的商品),观察超卖问题,思考如何用乐观锁(
@Version)或悲观锁(SELECT FOR UPDATE)解决。