文章目录
引言
事务传播行为是Spring事务管理框架中的核心概念,它定义了当一个事务方法被另一个事务方法调用时应该如何处理事务边界。正确理解和应用这些传播行为对于确保数据一致性至关重要。本文将深入解析Spring提供的七种事务传播机制,通过实例代码和典型应用场景的分析,帮助开发者正确选择合适的传播行为,构建健壮的企业级应用。
一、事务传播行为概述
事务传播行为指的是一个事务方法被另一个事务方法调用时,这个方法是否应该在现有事务中运行,还是开启新事务,或者是以非事务方式运行。Spring通过TransactionDefinition接口定义了七种不同的传播行为,它们通过Propagation枚举类型表示,分别是REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER和NESTED。在使用@Transactional注解或XML配置事务时,可以指定propagation属性来设置事务的传播行为。
// 事务传播行为定义
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
// @Transactional注解使用传播行为示例
@Transactional(propagation = Propagation.REQUIRED)
public void someBusinessMethod() {
// 业务逻辑
}
二、REQUIRED传播行为
REQUIRED是Spring默认的传播行为,也是最常用的一种。当一个REQUIRED传播级别的方法被调用时,如果当前存在事务,则方法会加入到这个事务中;如果不存在事务,则会为该方法创建一个新的事务。这种行为确保了方法总是在事务中执行,无论调用方是否已开启事务。REQUIRED传播行为适用于大多数业务场景,特别是那些需要原子操作的业务方法。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleService roleService;
// 默认使用REQUIRED传播行为
@Transactional
public User createUserWithRoles(User user, List<Role> roles) {
// 保存用户
User savedUser = userRepository.save(user);
// 调用RoleService的方法,该方法也具有事务特性
// 由于使用REQUIRED传播行为,roleService.assignRolesToUser会加入到当前事务中
roleService.assignRolesToUser(savedUser, roles);
return savedUser;
}
}
@Service
public class RoleService {
@Autowired
private UserRoleRepository userRoleRepository;
@Transactional // 默认REQUIRED
public void assignRolesToUser(User user, List<Role> roles) {
for (Role role : roles) {
UserRole userRole = new UserRole(user, role);
userRoleRepository.save(userRole);
}
}
}
在上面的例子中,如果assignRolesToUser
方法发生异常,整个createUserWithRoles
事务都会回滚,用户数据也不会保存。这是因为它们共享同一个事务。
三、SUPPORTS传播行为
SUPPORTS传播行为表示当前方法支持事务,但不要求事务。如果当前存在事务,则方法会在事务中运行;如果不存在事务,则方法以非事务方式执行。这种传播行为适用于那些既可以在事务中执行也可以不在事务中执行的方法,通常是一些查询操作。
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
// 使用SUPPORTS传播行为
@Transactional(propagation = Propagation.SUPPORTS)
public List<Product> findProductsByCategory(String category) {
return productRepository.findByCategory(category);
}
@Transactional
public void updateProductInfo(Product product) {
// 先查询产品
Product existingProduct = findProductById(product.getId()); // 这里调用使用SUPPORTS的方法
// 更新产品信息
existingProduct.setName(product.getName());
existingProduct.setPrice(product.getPrice());
productRepository.save(existingProduct);
}
@Transactional(propagation = Propagation.SUPPORTS)
public Product findProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException("Product not found"));
}
}
在这个例子中,findProductsByCategory
和findProductById
方法使用SUPPORTS传播行为。当它们被updateProductInfo
这样的事务方法调用时,会在事务中执行;当它们被非事务方法调用时,会以非事务方式执行。
四、MANDATORY传播行为
MANDATORY传播行为要求当前必须存在事务,否则抛出异常。这种传播行为用于那些必须在事务中执行,但自身不应该创建事务的方法。MANDATORY通常用于服务层方法被另一个事务性方法调用的情况,确保方法不会单独被调用。
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
// 使用MANDATORY传播行为
@Transactional(propagation = Propagation.MANDATORY)
public void processPayment(Payment payment) {
validatePayment(payment);
paymentRepository.save(payment);
}
private void validatePayment(Payment payment) {
if (payment.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Payment amount must be greater than zero");
}
}
}
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
@Transactional
public Order createOrderWithPayment(Order order, Payment payment) {
// 保存订单
Order savedOrder = orderRepository.save(order);
// 设置支付关联的订单
payment.setOrderId(savedOrder.getId());
// 处理支付,由于PaymentService.processPayment使用MANDATORY,
// 它会在当前事务中执行,若没有事务会抛出异常
paymentService.processPayment(payment);
return savedOrder;
}
}
在这个例子中,processPayment
方法使用MANDATORY传播行为,确保它只能在已存在的事务中被调用。如果尝试直接调用processPayment
而不是通过createOrderWithPayment
,将会抛出IllegalTransactionStateException
异常。
五、REQUIRES_NEW传播行为
REQUIRES_NEW传播行为总是开启一个新的事务,如果当前已存在事务,则将其挂起,等新事务完成后再恢复。这意味着REQUIRES_NEW方法的事务独立于调用方的事务,它们的提交或回滚互不影响。这种传播行为适用于需要独立提交的操作,如记录审计日志、发送通知等。
@Service
public class AuditService {
@Autowired
private AuditLogRepository auditLogRepository;
// 使用REQUIRES_NEW传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAudit(String action, String username, String details) {
AuditLog log = new AuditLog();
log.setAction(action);
log.setUsername(username);
log.setDetails(details);
log.setTimestamp(new Date());
auditLogRepository.save(log);
}
}
@Service
public class UserManagementService {
@Autowired
private UserRepository userRepository;
@Autowired
private AuditService auditService;
@Transactional
public void deleteUser(Long userId, String adminUsername) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("User not found"));
// 记录审计日志,使用REQUIRES_NEW确保即使用户删除失败,审计日志也会保存
auditService.logAudit("DELETE_USER", adminUsername,
"Deleted user: " + user.getUsername());
// 删除用户
userRepository.delete(user);
// 如果这里抛出异常,主事务会回滚,但审计日志事务已经提交,不会回滚
if (user.isSystemAdmin()) {
throw new IllegalOperationException("Cannot delete system admin user");
}
}
}
在这个例子中,即使deleteUser
方法因抛出异常而回滚,logAudit
方法的事务也已经独立提交,确保了审计记录的完整性。
六、NOT_SUPPORTED传播行为
NOT_SUPPORTED传播行为表示方法不支持事务,如果当前存在事务,则将其挂起,方法以非事务方式执行。这种传播行为适用于那些不需要事务且可能影响事务性能的方法,如一些长时间运行的只读操作。
@Service
public class ReportService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 使用NOT_SUPPORTED传播行为
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public List<SalesReport> generateMonthlySalesReport(int year, int month) {
// 这是一个复杂且耗时的报表查询,使用NOT_SUPPORTED以避免长时间持有数据库事务
String sql = "SELECT p.product_name, SUM(oi.quantity) as total_quantity, " +
"SUM(oi.quantity * oi.price) as total_amount " +
"FROM order_items oi " +
"JOIN products p ON oi.product_id = p.id " +
"JOIN orders o ON oi.order_id = o.id " +
"WHERE YEAR(o.order_date) = ? AND MONTH(o.order_date) = ? " +
"GROUP BY p.product_name";
return jdbcTemplate.query(sql,
new Object[]{year, month},
(rs, rowNum) -> new SalesReport(
rs.getString("product_name"),
rs.getInt("total_quantity"),
rs.getBigDecimal("total_amount")
)
);
}
}
@Service
public class OrderAnalysisService {
@Autowired
private ReportService reportService;
@Transactional
public void analyzeOrderData(int year, int month) {
// 生成销售报表,这一步会挂起当前事务,以非事务方式执行
List<SalesReport> salesReports = reportService.generateMonthlySalesReport(year, month);
// 处理报表数据...
}
}
在这个例子中,generateMonthlySalesReport
方法使用NOT_SUPPORTED传播行为,确保它在被事务方法调用时不会长时间占用数据库事务资源。
七、NEVER传播行为
NEVER传播行为表示方法绝不在事务中执行,如果当前存在事务,则抛出异常。这种传播行为用于确保方法在非事务环境中调用,通常用于校验或只读操作,防止它们被错误地包含在事务中。
@Service
public class ConfigurationService {
@Autowired
private SystemConfigRepository configRepository;
// 使用NEVER传播行为
@Transactional(propagation = Propagation.NEVER)
public Map<String, String> loadAllConfigurations() {
List<SystemConfig> configs = configRepository.findAll();
Map<String, String> configMap = new HashMap<>();
for (SystemConfig config : configs) {
configMap.put(config.getKey(), config.getValue());
}
return configMap;
}
}
@Service
public class ApplicationService {
@Autowired
private ConfigurationService configService;
// 非事务方法
public void initializeApplication() {
// 这里可以调用configService.loadAllConfigurations()
Map<String, String> configs = configService.loadAllConfigurations();
// 处理配置...
}
@Transactional
public void updateApplicationSettings(Map<String, String> newSettings) {
// 这里调用configService.loadAllConfigurations()会抛出异常,
// 因为当前存在事务,而该方法使用NEVER传播行为
// 错误示例:
// Map<String, String> configs = configService.loadAllConfigurations();
}
}
在这个例子中,loadAllConfigurations
方法使用NEVER传播行为,确保它只在非事务环境中被调用。如果在事务方法中调用它,将会抛出IllegalTransactionStateException
异常。
八、NESTED传播行为
NESTED传播行为创建一个嵌套事务,如果当前存在事务。嵌套事务是当前事务的一个保存点,如果嵌套事务失败,可以回滚到这个保存点,而外部事务可以继续执行。如果不存在当前事务,则行为与REQUIRED相同。NESTED传播行为适用于可以部分回滚的操作,它提供了比REQUIRES_NEW更细粒度的控制,同时又避免了创建完全独立事务的开销。
需要注意的是,NESTED传播行为依赖于特定的事务管理器实现,如DataSourceTransactionManager,而JtaTransactionManager不支持嵌套事务。
@Service
public class OrderProcessingService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private LineItemService lineItemService;
@Transactional
public Order createOrder(Order order, List<LineItem> lineItems) {
// 保存订单
Order savedOrder = orderRepository.save(order);
try {
// 处理订单项,使用NESTED传播行为
lineItemService.processLineItems(savedOrder, lineItems);
} catch (Exception e) {
// 捕获异常,记录日志
System.err.println("Failed to process line items: " + e.getMessage());
// 不重新抛出异常,允许事务继续
}
// 即使lineItemService.processLineItems失败,
// 订单依然会被保存,因为我们捕获了异常且没有重新抛出
return savedOrder;
}
}
@Service
public class LineItemService {
@Autowired
private LineItemRepository lineItemRepository;
@Autowired
private InventoryService inventoryService;
// 使用NESTED传播行为
@Transactional(propagation = Propagation.NESTED)
public void processLineItems(Order order, List<LineItem> lineItems) {
for (LineItem item : lineItems) {
item.setOrder(order);
lineItemRepository.save(item);
// 检查并更新库存
boolean available = inventoryService.checkAndUpdateInventory(
item.getProductId(), item.getQuantity()
);
if (!available) {
throw new InsufficientInventoryException(
"Not enough inventory for product: " + item.getProductId()
);
}
}
}
}
在这个例子中,processLineItems
方法使用NESTED传播行为。如果处理订单项时出现异常,例如库存不足,那么只有processLineItems
方法的操作会回滚,而订单本身仍然会被保存。这提供了更细粒度的错误处理能力。
总结
Spring事务管理框架提供的七种传播行为为开发者处理复杂的事务场景提供了强大的工具。REQUIRED适用于大多数业务操作,SUPPORTS适合只读查询,MANDATORY确保方法在事务中执行,REQUIRES_NEW创建独立事务,NOT_SUPPORTED用于非事务操作,NEVER禁止在事务中执行,NESTED提供细粒度的事务控制。在实际开发中,需要根据业务需求和方法特性选择合适的传播行为,以确保数据的一致性和完整性。了解这些传播行为的工作机制和适用场景,是构建健壮的企业级应用的关键。合理使用事务传播行为,可以优化系统性能,提高代码可维护性,增强应用的健壮性。