Spring事務失效的十大陷阱與終極解決方案
1. 事務管理基礎:Spring事務工作原理深度解析
在深入問題之前,我們先完整理解Spring聲明式事務的工作機制:
1.1 Spring事務架構概覽
// Spring事務管理的核心組件關系
@Component
public class TransactionArchitecture {
/*
* 事務管理核心流程:
* 1. 解析@Transactional注解
* 2. 創(chuàng)建AOP代理
* 3. 事務攔截器處理
* 4. 事務管理器協(xié)調
* 5. 連接資源管理
*/
}
// 事務攔截器核心邏輯(簡化版)
public class TransactionInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 1. 獲取事務屬性
TransactionAttribute txAttr = getTransactionAttributeSource()
.getTransactionAttribute(invocation.getMethod(), invocation.getClass());
// 2. 創(chuàng)建或加入事務
TransactionStatus status = transactionManager.getTransaction(txAttr);
try {
// 3. 執(zhí)行業(yè)務方法
Object result = invocation.proceed();
// 4. 提交事務
transactionManager.commit(status);
return result;
} catch (Exception ex) {
// 5. 回滾處理
completeTransactionAfterThrowing(txAttr, status, ex);
throw ex;
}
}
}
1.2 完整的事務配置示例
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager =
new DataSourceTransactionManager(dataSource);
// 完整的事務管理器配置
transactionManager.setNestedTransactionAllowed(true);
transactionManager.setRollbackOnCommitFailure(false);
transactionManager.setDefaultTimeout(30); // 30秒超時
return transactionManager;
}
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
template.setTimeout(30);
return template;
}
}
2. 陷阱一:同類方法調用(最常見的坑)
2.1 問題完整復現(xiàn)
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
/**
* 創(chuàng)建訂單的主方法
*/
public OrderCreateResult createOrder(OrderCreateRequest request) {
log.info("開始創(chuàng)建訂單: {}", request.getOrderNo());
// 1. 驗證訂單
validateOrder(request);
// 2. 保存訂單(期望在事務中)
Order order = saveOrder(request); // ? 事務失效點
// 3. 更新庫存
updateInventory(order);
// 4. 記錄日志
logOrderCreate(order);
return new OrderCreateResult(true, "創(chuàng)建成功", order.getId());
}
/**
* 保存訂單數(shù)據(jù) - 期望在事務中執(zhí)行
*/
@Transactional
public Order saveOrder(OrderCreateRequest request) {
log.info("保存訂單數(shù)據(jù)");
Order order = convertToOrder(request);
order.setStatus(OrderStatus.CREATED);
// 保存訂單主表
Order savedOrder = orderRepository.save(order);
// 保存訂單明細
saveOrderItems(request.getItems(), savedOrder.getId());
// 模擬一個業(yè)務異常
if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException("訂單金額必須大于0");
}
return savedOrder;
}
/**
* 保存訂單明細
*/
@Transactional(propagation = Propagation.REQUIRED)
private void saveOrderItems(List<OrderItem> items, Long orderId) {
for (OrderItem item : items) {
item.setOrderId(orderId);
orderRepository.saveItem(item);
}
}
/**
* 更新庫存
*/
private void updateInventory(Order order) {
inventoryService.deductInventory(order);
}
/**
* 記錄訂單創(chuàng)建日志
*/
private void logOrderCreate(Order order) {
// 記錄操作日志
System.out.println("訂單創(chuàng)建完成: " + order.getId());
}
}
2.2 問題深度分析
/**
* 問題分析服務 - 通過AOP原理解釋為什么事務失效
*/
@Component
@Aspect
@Slf4j
public class TransactionAnalysisAspect {
/**
* 模擬Spring AOP代理的工作方式
*/
public void demonstrateProxyMechanism() {
/*
* 實際Spring創(chuàng)建的代理對象結構:
*
* ProxyOrderService (Spring AOP代理)
* - target: RealOrderService (原始對象)
* - advisors: [TransactionInterceptor]
*
* 當調用 proxy.saveOrder() 時:
* 1. 代理攔截方法調用
* 2. 執(zhí)行TransactionInterceptor
* 3. 開啟事務
* 4. 調用target.saveOrder()
* 5. 提交/回滾事務
*
* 當在同一個類中調用 this.saveOrder() 時:
* 1. 直接調用原始對象的方法
* 2. 繞過代理攔截器
* 3. 事務注解失效
*/
}
@Around("@annotation(transactional)")
public Object analyzeTransaction(ProceedingJoinPoint joinPoint,
Transactional transactional) throws Throwable {
String methodName = joinPoint.getSignature().getName();
boolean isTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("方法 {} 調用前,事務狀態(tài): {}", methodName, isTransactionActive);
Object result = joinPoint.proceed();
isTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("方法 {} 調用后,事務狀態(tài): {}", methodName, isTransactionActive);
return result;
}
}
2.3 完整解決方案
@Service
@Slf4j
public class CorrectOrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Autowired
private ApplicationContext applicationContext;
/**
* 方案1:通過ApplicationContext獲取代理對象
*/
public OrderCreateResult createOrderV1(OrderCreateRequest request) {
validateOrder(request);
// 獲取代理對象
CorrectOrderService proxy = applicationContext.getBean(CorrectOrderService.class);
Order order = proxy.saveOrder(request); // ? 通過代理調用
updateInventory(order);
logOrderCreate(order);
return new OrderCreateResult(true, "創(chuàng)建成功", order.getId());
}
/**
* 方案2:自我注入代理對象
*/
@Autowired
@Lazy // 避免循環(huán)依賴問題
private CorrectOrderService self;
public OrderCreateResult createOrderV2(OrderCreateRequest request) {
validateOrder(request);
Order order = self.saveOrder(request); // ? 通過注入的代理調用
updateInventory(order);
logOrderCreate(order);
return new OrderCreateResult(true, "創(chuàng)建成功", order.getId());
}
/**
* 方案3:重構方法結構,事務方法作為入口
*/
@Transactional
public OrderCreateResult createOrderV3(OrderCreateRequest request) {
validateOrder(request);
Order order = saveOrderInternal(request); // ? 在事務方法內調用
updateInventory(order);
logOrderCreate(order);
return new OrderCreateResult(true, "創(chuàng)建成功", order.getId());
}
/**
* 方案4:使用編程式事務
*/
@Autowired
private TransactionTemplate transactionTemplate;
public OrderCreateResult createOrderV4(OrderCreateRequest request) {
validateOrder(request);
Order order = transactionTemplate.execute(status -> {
try {
return saveOrderInternal(request);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
updateInventory(order);
logOrderCreate(order);
return new OrderCreateResult(true, "創(chuàng)建成功", order.getId());
}
@Transactional
public Order saveOrder(OrderCreateRequest request) {
return saveOrderInternal(request);
}
/**
* 內部保存邏輯 - 不添加事務注解
*/
private Order saveOrderInternal(OrderCreateRequest request) {
log.info("保存訂單數(shù)據(jù)");
Order order = convertToOrder(request);
order.setStatus(OrderStatus.CREATED);
Order savedOrder = orderRepository.save(order);
saveOrderItems(request.getItems(), savedOrder.getId());
if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException("訂單金額必須大于0");
}
return savedOrder;
}
// 其他輔助方法...
private void saveOrderItems(List<OrderItem> items, Long orderId) {
for (OrderItem item : items) {
item.setOrderId(orderId);
orderRepository.saveItem(item);
}
}
private void validateOrder(OrderCreateRequest request) {
// 驗證邏輯
}
private void updateInventory(Order order) {
inventoryService.deductInventory(order);
}
private void logOrderCreate(Order order) {
// 日志記錄
}
}
3. 陷阱二:異常處理不當
3.1 異常處理完整示例
@Service
@Slf4j
public class ExceptionHandlingService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
/**
* 場景1:捕獲異常導致事務不回滾
*/
@Transactional
public void registerUserV1(User user) {
try {
// 保存用戶
userRepository.save(user);
// 發(fā)送歡迎郵件(可能拋出異常)
emailService.sendWelcomeEmail(user.getEmail());
} catch (Exception e) {
// ? 捕獲了所有異常,事務不會回滾
log.error("用戶注冊失敗: {}", user.getUsername(), e);
// 用戶記錄已保存,但郵件發(fā)送失敗,數(shù)據(jù)不一致
}
}
/**
* 場景2:檢查異常默認不回滾
*/
@Transactional
public void uploadUserAvatarV1(User user, InputStream avatarStream) throws IOException {
userRepository.update(user);
// 上傳頭像(拋出IOException)
fileService.uploadAvatar(user.getId(), avatarStream); // ? IOException不會觸發(fā)回滾
// 如果上傳失敗,用戶信息已更新,但頭像缺失
}
/**
* 場景3:錯誤的異常類型指定
*/
@Transactional(rollbackFor = RuntimeException.class) // 默認值,但業(yè)務異??赡芾^承Exception
public void processBusinessV1(BusinessRequest request) {
userRepository.updateStatus(request.getUserId(), "PROCESSING");
// 業(yè)務處理,拋出自定義業(yè)務異常
businessService.process(request); // ? 如果BusinessException繼承Exception,不會回滾
userRepository.updateStatus(request.getUserId(), "COMPLETED");
}
/**
* 正確解決方案
*/
/**
* 方案1:正確配置回滾異常
*/
@Transactional(rollbackFor = Exception.class) // ? 所有異常都回滾
public void registerUserV2(User user) {
try {
userRepository.save(user);
emailService.sendWelcomeEmail(user.getEmail());
} catch (EmailException e) {
// 只捕獲郵件發(fā)送異常,不影響事務
log.warn("歡迎郵件發(fā)送失敗: {}", user.getEmail(), e);
// 郵件發(fā)送失敗不影響用戶注冊,事務繼續(xù)提交
} catch (Exception e) {
// 其他異常重新拋出,觸發(fā)回滾
log.error("用戶注冊失敗: {}", user.getUsername(), e);
throw new BusinessException("用戶注冊失敗", e);
}
}
/**
* 方案2:分層異常處理
*/
@Transactional(rollbackFor = BusinessException.class)
public void uploadUserAvatarV2(User user, InputStream avatarStream) {
try {
userRepository.update(user);
fileService.uploadAvatar(user.getId(), avatarStream);
} catch (IOException e) {
// 將檢查異常轉換為運行時異常
throw new BusinessException("頭像上傳失敗", e);
}
}
/**
* 方案3:編程式異??刂?
*/
@Transactional
public void processBusinessV2(BusinessRequest request) {
TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
try {
userRepository.updateStatus(request.getUserId(), "PROCESSING");
businessService.process(request);
userRepository.updateStatus(request.getUserId(), "COMPLETED");
} catch (BusinessException e) {
// 手動標記回滾
status.setRollbackOnly();
log.error("業(yè)務處理失敗: {}", request.getRequestId(), e);
throw e;
}
}
/**
* 方案4:自定義異?;貪L策略
*/
@Component
public class CustomRollbackPolicy {
@Autowired
private PlatformTransactionManager transactionManager;
public void executeWithRollbackPolicy(Runnable businessLogic,
Class<? extends Exception>... rollbackExceptions) {
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute();
transactionAttribute.setRollbackRules(Arrays.asList(rollbackExceptions));
TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
try {
businessLogic.run();
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
}
4. 陷阱三:事務傳播機制誤用
4.1 傳播機制完整示例
@Service
@Slf4j
public class PropagationService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private LogRepository logRepository;
@Autowired
private PropagationService self;
/**
* 錯誤示例:錯誤使用REQUIRED傳播
*/
@Transactional
public void processBatchOrdersV1(List<Order> orders) {
int successCount = 0;
int failureCount = 0;
for (Order order : orders) {
try {
// ? 每個訂單處理在同一個事務中
processSingleOrder(order);
successCount++;
} catch (Exception e) {
failureCount++;
log.error("訂單處理失敗: {}", order.getOrderNo(), e);
// 一個訂單失敗會導致整個批次回滾
}
}
log.info("批次處理完成: 成功={}, 失敗={}", successCount, failureCount);
}
@Transactional(propagation = Propagation.REQUIRED)
public void processSingleOrder(Order order) {
// 訂單處理邏輯
orderRepository.save(order);
inventoryService.deductStock(order);
if (order.getAmount().compareTo(BigDecimal.valueOf(10000)) > 0) {
throw new BusinessException("訂單金額超限");
}
}
/**
* 正確使用傳播機制
*/
/**
* 方案1:使用REQUIRES_NEW實現(xiàn)事務隔離
*/
public BatchProcessResult processBatchOrdersV2(List<Order> orders) {
BatchProcessResult result = new BatchProcessResult();
for (Order order : orders) {
try {
// ? 每個訂單在獨立事務中處理
self.processSingleOrderInNewTx(order);
result.incrementSuccess();
} catch (Exception e) {
result.incrementFailure();
log.error("訂單處理失敗: {}", order.getOrderNo(), e);
// 單個訂單失敗不影響其他訂單
}
}
// 記錄批次日志(在獨立事務中)
self.logBatchResult(result);
return result;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processSingleOrderInNewTx(Order order) {
orderRepository.save(order);
inventoryService.deductStock(order);
if (order.getAmount().compareTo(BigDecimal.valueOf(10000)) > 0) {
throw new BusinessException("訂單金額超限");
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logBatchResult(BatchProcessResult result) {
logRepository.saveBatchLog(result);
}
/**
* 方案2:使用NESTED傳播(需要支持保存點)
*/
@Transactional
public void processOrderWithNestedOperations(Order order) {
// 主訂單處理
orderRepository.save(order);
try {
// ? 嵌套事務:可以獨立回滾
self.processOrderItems(order.getItems());
} catch (Exception e) {
log.error("訂單明細處理失敗,繼續(xù)處理其他邏輯", e);
// 明細處理失敗不影響主訂單
}
// 繼續(xù)處理其他邏輯
updateOrderStatistics(order);
}
@Transactional(propagation = Propagation.NESTED)
public void processOrderItems(List<OrderItem> items) {
for (OrderItem item : items) {
itemRepository.save(item);
if (item.getQuantity() <= 0) {
throw new BusinessException("商品數(shù)量必須大于0");
}
}
}
/**
* 方案3:使用NOT_SUPPORTED暫停事務
*/
@Transactional
public void generateReportWithHeavyOperation() {
// 輕量級數(shù)據(jù)庫操作
List<ReportData> data = reportRepository.getReportData();
// ? 暫停事務,執(zhí)行重量級操作
String reportFile = self.generateLargeReportFile(data);
// 恢復事務,保存報告記錄
reportRepository.saveReportRecord(reportFile);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public String generateLargeReportFile(List<ReportData> data) {
// 生成大型報告文件(耗時操作)
// 不在事務中,避免長事務和鎖競爭
return reportService.generateExcelReport(data);
}
/**
* 傳播機制使用指南
*/
public class PropagationGuide {
/*
* REQUIRED(默認):如果當前存在事務,則加入該事務;如果當前沒有事務,則創(chuàng)建一個新的事務
* 適用場景:大多數(shù)業(yè)務方法
*
* REQUIRES_NEW:創(chuàng)建一個新的事務,如果當前存在事務,則把當前事務掛起
* 適用場景:需要獨立提交/回滾的子任務
*
* NESTED:如果當前存在事務,則創(chuàng)建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則創(chuàng)建一個新的事務
* 適用場景:可部分回滾的子操作
*
* SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續(xù)運行
* 適用場景:查詢方法,可有可無事務
*
* NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起
* 適用場景:非事務性操作,如文件處理、遠程調用
*
* NEVER:以非事務方式運行,如果當前存在事務,則拋出異常
* 適用場景:強制非事務環(huán)境
*
* MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常
* 適用場景:強制要求事務環(huán)境
*/
}
}
5. 陷阱四:訪問權限限制
5.1 訪問權限完整示例
@Service
@Slf4j
public class AccessControlService {
/**
* 錯誤示例:非public方法使用事務注解
*/
@Transactional
void internalProcessV1() { // ? package-private方法
// 事務不會生效
repository.updateData();
}
@Transactional
protected void protectedProcessV1() { // ? protected方法
// 事務不會生效
repository.updateData();
}
@Transactional
private void privateProcessV1() { // ? private方法
// 事務不會生效
repository.updateData();
}
/**
* 解決方案
*/
/**
* 方案1:正確的訪問權限
*/
@Transactional
public void publicProcess() { // ? public方法
repository.updateData();
}
/**
* 方案2:門面模式 + 內部方法調用
*/
public void complexBusinessProcess() {
// 步驟1:非事務性預處理
preProcess();
// 步驟2:事務性核心處理
transactionalCoreProcess();
// 步驟3:非事務性后處理
postProcess();
}
@Transactional
public void transactionalCoreProcess() {
// 核心業(yè)務邏輯
step1();
step2();
step3();
}
// 內部方法 - 不添加事務注解
private void preProcess() {
// 數(shù)據(jù)驗證、參數(shù)校驗等
}
private void step1() {
// 步驟1邏輯
}
private void step2() {
// 步驟2邏輯
}
private void step3() {
// 步驟3邏輯
}
private void postProcess() {
// 清理、通知等
}
/**
* 方案3:使用AspectJ模式(需要額外配置)
*/
@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ) // 使用AspectJ代理
public class AspectJTransactionConfig {
// AspectJ模式下,非public方法的事務注解也會生效
// 但需要編譯時或加載時織入
}
@Transactional
protected void aspectJProtectedProcess() {
// 在AspectJ模式下,protected方法的事務會生效
repository.updateData();
}
}
6. 陷阱五:數(shù)據(jù)庫配置問題
6.1 數(shù)據(jù)庫配置完整示例
@Service
@Slf4j
public class DatabaseConfigService {
/**
* 錯誤示例:使用不支持事務的存儲引擎
*/
@Entity
@Table(name = "operation_logs")
public class OperationLog {
// 如果數(shù)據(jù)庫表使用MyISAM引擎,事務將失效
}
@Transactional
public void batchInsertLogsV1(List<OperationLog> logs) {
for (OperationLog log : logs) {
logRepository.save(log); // ? MyISAM不支持事務
}
// 即使發(fā)生異常,已插入的數(shù)據(jù)也不會回滾
}
/**
* 解決方案
*/
/**
* 方案1:確保使用InnoDB引擎
*/
@Entity
@Table(name = "operation_logs",
options = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
public class OperationLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String content;
@CreationTimestamp
private LocalDateTime createTime;
}
/**
* 方案2:數(shù)據(jù)庫表DDL驗證
*/
@Component
public class TableEngineValidator {
@Autowired
private JdbcTemplate jdbcTemplate;
@PostConstruct
public void validateTableEngines() {
String sql = "SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES " +
"WHERE TABLE_SCHEMA = DATABASE() AND ENGINE != 'InnoDB'";
List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
if (!results.isEmpty()) {
log.warn("發(fā)現(xiàn)非InnoDB引擎的表: {}", results);
// 可以拋出異?;蛴涗浘?
}
}
}
/**
* 方案3:編程式事務 + 手動回滾補償
*/
@Service
public class CompensationService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void batchInsertWithCompensation(List<OperationLog> logs) {
List<Long> insertedIds = new ArrayList<>();
try {
for (OperationLog log : logs) {
// 手動插入并記錄ID
Long id = insertLogAndReturnId(log);
insertedIds.add(id);
}
// 業(yè)務驗證
validateBusinessRules(logs);
} catch (Exception e) {
// 手動回滾:刪除已插入的記錄
rollbackInsertedLogs(insertedIds);
throw new BusinessException("批量插入失敗,已回滾", e);
}
}
private Long insertLogAndReturnId(OperationLog log) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO operation_logs (content) VALUES (?)",
Statement.RETURN_GENERATED_KEYS
);
ps.setString(1, log.getContent());
return ps;
}, keyHolder);
return keyHolder.getKey().longValue();
}
private void rollbackInsertedLogs(List<Long> ids) {
if (!ids.isEmpty()) {
String deleteSql = "DELETE FROM operation_logs WHERE id IN (" +
ids.stream().map(String::valueOf).collect(Collectors.joining(",")) + ")";
jdbcTemplate.update(deleteSql);
}
}
}
}
7. 陷阱六:連接池配置問題
7.1 連接池配置完整示例
@Configuration
public class DataSourceConfig {
/**
* 錯誤示例:自動提交設置沖突
*/
@Bean
public DataSource wrongDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
// ? 連接池自動提交與事務管理沖突
dataSource.setAutoCommit(true);
return dataSource;
}
/**
* 正確配置
*/
@Bean
@Primary
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
// ? 正確的連接池配置
dataSource.setAutoCommit(false); // 讓Spring管理事務提交
dataSource.setConnectionTimeout(30000);
dataSource.setIdleTimeout(600000);
dataSource.setMaxLifetime(1800000);
dataSource.setMaximumPoolSize(20);
dataSource.setMinimumIdle(5);
// 事務相關配置
dataSource.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
dataSource.setLeakDetectionThreshold(60000);
return dataSource;
}
/**
* 連接池監(jiān)控
*/
@Component
public class DataSourceMonitor {
@Autowired
private DataSource dataSource;
@Scheduled(fixedRate = 30000) // 每30秒監(jiān)控一次
public void monitorDataSource() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
HikariPoolMXBean pool = hikariDataSource.getHikariPoolMXBean();
log.info("連接池狀態(tài): 活躍={}, 空閑={}, 等待={}, 總數(shù)={}",
pool.getActiveConnections(),
pool.getIdleConnections(),
pool.getThreadsAwaitingConnection(),
pool.getTotalConnections());
// 檢查連接泄漏
if (pool.getActiveConnections() > 15) {
log.warn("活躍連接數(shù)過高,可能存在連接泄漏");
}
}
}
}
}
8. 陷阱七:多數(shù)據(jù)源事務混淆
8.1 多數(shù)據(jù)源完整配置
@Configuration
public class MultiDataSourceConfig {
/**
* 主數(shù)據(jù)源
*/
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
/**
* 從數(shù)據(jù)源
*/
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
/**
* 主數(shù)據(jù)源事務管理器
*/
@Bean
@Primary
public PlatformTransactionManager primaryTransactionManager() {
return new DataSourceTransactionManager(primaryDataSource());
}
/**
* 從數(shù)據(jù)源事務管理器
*/
@Bean
public PlatformTransactionManager secondaryTransactionManager() {
return new DataSourceTransactionManager(secondaryDataSource());
}
/**
* JPA實體管理器工廠
*/
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource())
.packages("com.example.primary.entity")
.persistenceUnit("primary")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(secondaryDataSource())
.packages("com.example.secondary.entity")
.persistenceUnit("secondary")
.build();
}
}
/**
* 多數(shù)據(jù)源服務示例
*/
@Service
@Slf4j
public class MultiDataSourceService {
@Autowired
@Qualifier("primaryTransactionManager")
private PlatformTransactionManager primaryTxManager;
@Autowired
@Qualifier("secondaryTransactionManager")
private PlatformTransactionManager secondaryTxManager;
@Autowired
private PrimaryRepository primaryRepository;
@Autowired
private SecondaryRepository secondaryRepository;
/**
* 錯誤示例:錯誤的事務管理器使用
*/
@Transactional // ? 使用默認事務管理器,只能管理主數(shù)據(jù)源
public void crossDataSourceOperationV1(CrossDataRequest request) {
// 操作主數(shù)據(jù)源
primaryRepository.save(request.getPrimaryData());
// 操作從數(shù)據(jù)源 - 不在事務管理中!
secondaryRepository.save(request.getSecondaryData());
// 如果這里發(fā)生異常,主數(shù)據(jù)源會回滾,但從數(shù)據(jù)源不會
}
/**
* 解決方案1:分別使用不同的事務管理器
*/
public void crossDataSourceOperationV2(CrossDataRequest request) {
// 主數(shù)據(jù)源操作
primaryTxManager.execute(status -> {
primaryRepository.save(request.getPrimaryData());
return null;
});
// 從數(shù)據(jù)源操作
secondaryTxManager.execute(status -> {
secondaryRepository.save(request.getSecondaryData());
return null;
});
// 每個操作在獨立事務中,但無法保證原子性
}
/**
* 解決方案2:使用分布式事務(如Atomikos)
*/
@Configuration
@EnableTransactionManagement
public static class JtaTransactionConfig {
@Bean
public JtaTransactionManager transactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
}
@Transactional // 使用JTA事務管理器
public void crossDataSourceOperationV3(CrossDataRequest request) {
// 在分布式事務中,兩個數(shù)據(jù)源的操作會一起提交或回滾
primaryRepository.save(request.getPrimaryData());
secondaryRepository.save(request.getSecondaryData());
}
/**
* 解決方案3:最終一致性模式
*/
public void crossDataSourceOperationV4(CrossDataRequest request) {
// 步驟1:主數(shù)據(jù)源操作
Long primaryId = savePrimaryData(request.getPrimaryData());
try {
// 步驟2:從數(shù)據(jù)源操作
saveSecondaryData(request.getSecondaryData(), primaryId);
// 步驟3:標記主數(shù)據(jù)完成
markPrimaryDataCompleted(primaryId);
} catch (Exception e) {
// 步驟4:補償操作
compensatePrimaryData(primaryId);
throw new BusinessException("跨數(shù)據(jù)源操作失敗", e);
}
}
@Transactional("primaryTransactionManager")
public Long savePrimaryData(PrimaryData data) {
PrimaryData saved = primaryRepository.save(data);
return saved.getId();
}
@Transactional("secondaryTransactionManager")
public void saveSecondaryData(SecondaryData data, Long primaryId) {
data.setPrimaryId(primaryId);
secondaryRepository.save(data);
}
@Transactional("primaryTransactionManager")
public void markPrimaryDataCompleted(Long primaryId) {
primaryRepository.updateStatus(primaryId, "COMPLETED");
}
@Transactional("primaryTransactionManager")
public void compensatePrimaryData(Long primaryId) {
primaryRepository.updateStatus(primaryId, "FAILED");
// 可以記錄補償日志等
}
}
9. 陷阱八:異步執(zhí)行事務丟失
9.1 異步事務完整示例
@Service
@Slf4j
@EnableAsync
public class AsyncTransactionService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private EmailService emailService;
@Autowired
private AsyncTransactionService self;
/**
* 錯誤示例:異步方法中的事務注解
*/
@Transactional
@Async
public void asyncProcessOrderV1(Order order) {
// ? 異步方法中的事務注解通常不會按預期工作
orderRepository.updateStatus(order.getId(), "PROCESSING");
// 復雜的業(yè)務處理
processComplexBusiness(order);
orderRepository.updateStatus(order.getId(), "COMPLETED");
// 事務上下文可能丟失,回滾機制不可靠
}
/**
* 解決方案1:同步處理核心事務,異步處理非核心操作
*/
@Transactional
public void processOrderV2(Order order) {
// 步驟1:同步處理核心業(yè)務(在事務中)
orderRepository.updateStatus(order.getId(), "PROCESSING");
processCoreBusiness(order);
orderRepository.updateStatus(order.getId(), "COMPLETED");
// 步驟2:異步處理非核心操作(不在事務中)
self.asyncSendNotifications(order);
}
@Async
public void asyncSendNotifications(Order order) {
try {
// 發(fā)送郵件通知
emailService.sendOrderCompleteEmail(order);
// 發(fā)送短信通知
smsService.sendOrderCompleteSms(order);
// 其他非核心操作
logService.recordOperationLog(order);
} catch (Exception e) {
// 異步操作失敗不影響主流程
log.error("異步通知發(fā)送失敗: {}", order.getId(), e);
}
}
/**
* 解決方案2:使用@TransactionalEventListener
*/
@Transactional
public void processOrderV3(Order order) {
// 核心業(yè)務處理
orderRepository.updateStatus(order.getId(), "PROCESSING");
processCoreBusiness(order);
orderRepository.updateStatus(order.getId(), "COMPLETED");
// 發(fā)布事務事件
applicationEventPublisher.publishEvent(new OrderCompletedEvent(this, order));
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
public void handleOrderCompletedEvent(OrderCompletedEvent event) {
// 只有在主事務提交后才會執(zhí)行
Order order = event.getOrder();
try {
emailService.sendOrderCompleteEmail(order);
smsService.sendOrderCompleteSms(order);
} catch (Exception e) {
log.error("訂單完成后續(xù)處理失敗: {}", order.getId(), e);
}
}
/**
* 解決方案3:編程式事務 + 異步執(zhí)行
*/
@Autowired
private TransactionTemplate transactionTemplate;
@Async
public void asyncProcessWithProgrammaticTx(Order order) {
// 在異步線程中創(chuàng)建新事務
transactionTemplate.execute(status -> {
try {
orderRepository.updateStatus(order.getId(), "PROCESSING");
processCoreBusiness(order);
orderRepository.updateStatus(order.getId(), "COMPLETED");
return null;
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
/**
* 異步配置
*/
@Configuration
@EnableAsync
public static class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsyncTx-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("異步方法執(zhí)行異常: {}.{}", method.getDeclaringClass().getName(), method.getName(), ex);
};
}
}
}
10. 陷阱九:事務超時與只讀設置
10.1 超時與只讀配置完整示例
@Service
@Slf4j
public class TimeoutReadonlyService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
/**
* 錯誤示例:長事務無超時設置
*/
@Transactional
public void generateComplexReportV1(ReportRequest request) {
// 步驟1:查詢大量數(shù)據(jù)
List<User> users = userRepository.findAllActiveUsers(); // 可能很慢
// 步驟2:復雜計算
processComplexCalculation(users); // 可能很慢
// 步驟3:生成報告
generateReportFile(users); // 可能很慢
// ? 沒有超時設置,可能長時間占用數(shù)據(jù)庫連接
}
/**
* 錯誤示例:寫操作使用只讀事務
*/
@Transactional(readOnly = true) // ? 只讀事務
public void updateUserStatisticsV1() {
// 嘗試在只讀事務中執(zhí)行寫操作
userRepository.updateStatistics(); // 可能失敗或行為異常
}
/**
* 正確配置示例
*/
/**
* 方案1:合理設置超時時間
*/
@Transactional(timeout = 30) // ? 30秒超時
public void generateComplexReportV2(ReportRequest request) {
try {
List<User> users = userRepository.findAllActiveUsers();
processComplexCalculation(users);
generateReportFile(users);
} catch (TransactionTimedOutException e) {
log.error("報告生成超時: {}", request, e);
throw new BusinessException("報告生成超時,請重試");
}
}
/**
* 方案2:只讀事務用于查詢操作
*/
@Transactional(readOnly = true, timeout = 10)
public List<UserReport> getUserReports() {
// 純查詢操作,使用只讀事務
return userRepository.generateUserReports();
}
/**
* 方案3:拆分長事務
*/
public void generateComplexReportV3(ReportRequest request) {
// 步驟1:快速查詢必要數(shù)據(jù)
List<Long> userIds = userRepository.findActiveUserIds();
// 步驟2:分批處理
Lists.partition(userIds, 1000).forEach(batch -> {
processUserBatch(batch, request);
});
}
@Transactional(timeout = 10)
public void processUserBatch(List<Long> userIds, ReportRequest request) {
List<User> users = userRepository.findByIdIn(userIds);
processBatchCalculation(users);
// 每個批次在獨立短事務中處理
}
/**
* 方案4:動態(tài)超時配置
*/
@Autowired
private TransactionTemplate transactionTemplate;
public void processWithDynamicTimeout(boolean isComplex) {
int timeout = isComplex ? 60 : 10;
TransactionTemplate dynamicTemplate = new TransactionTemplate(
transactionTemplate.getTransactionManager()
);
dynamicTemplate.setTimeout(timeout);
dynamicTemplate.execute(status -> {
// 業(yè)務邏輯
return null;
});
}
/**
* 事務監(jiān)控和診斷
*/
@Component
@Aspect
@Slf4j
public class TransactionMonitorAspect {
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Around("@annotation(transactional)")
public Object monitorTransaction(ProceedingJoinPoint joinPoint,
Transactional transactional) throws Throwable {
startTime.set(System.currentTimeMillis());
String methodName = joinPoint.getSignature().toShortString();
int timeout = transactional.timeout();
log.info("事務開始: {}, 超時時間: {}秒", methodName, timeout);
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime.get();
if (duration > timeout * 1000L) {
log.warn("事務執(zhí)行時間接近超時: {}ms, 方法: {}", duration, methodName);
}
return result;
} catch (TransactionTimedOutException e) {
log.error("事務超時: {}, 配置超時: {}秒", methodName, timeout, e);
throw e;
} finally {
startTime.remove();
}
}
}
}
11. 完整的事務測試方案
11.1 事務測試完整示例
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:testdb",
"spring.jpa.hibernate.ddl-auto=create-drop"
})
@Slf4j
class TransactionalTest {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private PlatformTransactionManager transactionManager;
@Test
void testTransactionRollbackOnException() {
// 準備測試數(shù)據(jù)
OrderCreateRequest request = createTestRequest();
request.setAmount(BigDecimal.ZERO); // 觸發(fā)異常
// 執(zhí)行測試
assertThrows(BusinessException.class, () -> {
orderService.createOrder(request);
});
// 驗證事務回滾
assertFalse(orderRepository.existsByOrderNo(request.getOrderNo()));
}
@Test
void testTransactionCommitSuccess() {
// 準備測試數(shù)據(jù)
OrderCreateRequest request = createTestRequest();
request.setAmount(BigDecimal.TEN);
// 執(zhí)行測試
OrderCreateResult result = orderService.createOrder(request);
// 驗證事務提交
assertTrue(result.isSuccess());
assertTrue(orderRepository.existsByOrderNo(request.getOrderNo()));
}
@Test
void testTransactionPropagation() {
TransactionStatus status = transactionManager.getTransaction(
new DefaultTransactionAttribute()
);
try {
// 驗證事務傳播
boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
assertTrue(isActive);
String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
assertNotNull(transactionName);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
@Test
void testTransactionTimeout() {
TransactionTemplate template = new TransactionTemplate(transactionManager);
template.setTimeout(1); // 1秒超時
assertThrows(TransactionTimedOutException.class, () -> {
template.execute(status -> {
// 模擬長時間操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return null;
});
});
}
/**
* 事務測試配置
*/
@TestConfiguration
static class TestConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("testdb")
.build();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
private OrderCreateRequest createTestRequest() {
OrderCreateRequest request = new OrderCreateRequest();
request.setOrderNo("TEST_" + System.currentTimeMillis());
request.setAmount(BigDecimal.valueOf(100));
request.setUserId(1L);
// 設置其他必要字段
return request;
}
}
12. 事務監(jiān)控與運維
12.1 完整的事務監(jiān)控方案
@Component
@Slf4j
public class TransactionMonitor {
@Autowired
private MeterRegistry meterRegistry;
private final Counter transactionCounter;
private final Timer transactionTimer;
private final Counter rollbackCounter;
public TransactionMonitor(MeterRegistry meterRegistry) {
this.transactionCounter = meterRegistry.counter("transaction.total");
this.transactionTimer = meterRegistry.timer("transaction.duration");
this.rollbackCounter = meterRegistry.counter("transaction.rollback");
}
@EventListener
public void monitorTransactionEvent(ApplicationEvent event) {
if (event instanceof TransactionCompletedEvent) {
transactionCounter.increment();
}
}
/**
* 事務健康檢查
*/
@Component
public class TransactionHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) dataSource;
HikariPoolMXBean pool = hikari.getHikariPoolMXBean();
int activeConnections = pool.getActiveConnections();
int maxPoolSize = hikari.getMaximumPoolSize();
Health.Builder status = activeConnections > maxPoolSize * 0.8 ?
Health.down() : Health.up();
return status
.withDetail("activeConnections", activeConnections)
.withDetail("idleConnections", pool.getIdleConnections())
.withDetail("totalConnections", pool.getTotalConnections())
.withDetail("threadsAwaiting", pool.getThreadsAwaitingConnection())
.build();
}
return Health.unknown().build();
} catch (Exception e) {
return Health.down(e).build();
}
}
}
/**
* 長事務檢測和告警
*/
@Component
public class LongTransactionDetector {
private final Map<String, Long> transactionStartTimes = new ConcurrentHashMap<>();
@Autowired
private PlatformTransactionManager transactionManager;
@Scheduled(fixedRate = 30000) // 每30秒檢查一次
public void detectLongTransactions() {
long currentTime = System.currentTimeMillis();
long threshold = 30000; // 30秒閾值
transactionStartTimes.entrySet().removeIf(entry -> {
long duration = currentTime - entry.getValue();
if (duration > threshold) {
log.warn("發(fā)現(xiàn)長事務: {}, 持續(xù)時間: {}ms", entry.getKey(), duration);
// 發(fā)送告警
alertLongTransaction(entry.getKey(), duration);
return true;
}
return false;
});
}
public void recordTransactionStart(String transactionId) {
transactionStartTimes.put(transactionId, System.currentTimeMillis());
}
public void recordTransactionEnd(String transactionId) {
transactionStartTimes.remove(transactionId);
}
private void alertLongTransaction(String transactionId, long duration) {
// 發(fā)送郵件、短信、釘釘?shù)雀婢?
log.error("長事務告警: {}, 持續(xù)時間: {}ms", transactionId, duration);
}
}
}
13. 總結:事務管理最佳實踐
13.1 事務設計原則
/**
* 事務管理最佳實踐總結
*/
public class TransactionBestPractices {
/*
* 1. 事務邊界原則
* - 事務應該圍繞業(yè)務用例,而不是技術細節(jié)
* - 保持事務簡短,避免長事務
* - 在服務層管理事務,而不是在DAO層
*/
/*
* 2. 異常處理原則
* - 明確指定回滾異常類型
* - 避免在事務中捕獲所有異常
* - 將檢查異常轉換為非檢查異常
*/
/*
* 3. 性能優(yōu)化原則
* - 查詢操作使用只讀事務
* - 合理設置事務超時時間
* - 避免在事務中進行遠程調用和IO操作
*/
/*
* 4. 復雜度控制原則
* - 避免嵌套事務的過度使用
* - 明確事務傳播行為
* - 使用編程式事務處理復雜場景
*/
/*
* 5. 監(jiān)控運維原則
* - 監(jiān)控事務執(zhí)行時間和成功率
* - 設置長事務告警
* - 定期審查事務配置
*/
}
13.2 事務配置檢查清單
@Component
public class TransactionChecklist {
/**
* 事務配置驗證
*/
public void validateTransactionConfiguration() {
checkPublicMethods();
checkExceptionConfiguration();
checkTimeoutSettings();
checkDataSourceConfiguration();
checkAopConfiguration();
}
private void checkPublicMethods() {
// 驗證所有@Transactional方法都是public
}
private void checkExceptionConfiguration() {
// 驗證回滾異常配置
}
private void checkTimeoutSettings() {
// 驗證超時時間配置
}
private void checkDataSourceConfiguration() {
// 驗證數(shù)據(jù)源和連接池配置
}
private void checkAopConfiguration() {
// 驗證AOP代理配置
}
}
通過以上完整的示例和解決方案,相信你已經對Spring事務失效的各種場景有了全面的理解。記住,事務管理不僅是技術問題,更是架構設計和業(yè)務理解的體現(xiàn)。
以上就是Spring事務失效的十大陷阱與終極解決方案的詳細內容,更多關于Spring事務失效陷阱與解決的資料請關注腳本之家其它相關文章!
相關文章
Java如何獲取一個隨機數(shù) Java猜數(shù)字小游戲
這篇文章主要為大家詳細介紹了Java如何獲取一個隨機數(shù),類似猜數(shù)字小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-03-03
Java實戰(zhàn)寵物店在線交易平臺的實現(xiàn)流程
讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+Springboot+maven+Mysql+FreeMarker實現(xiàn)一個寵物在線交易系統(tǒng),大家可以在過程中查缺補漏,提升水平2022-01-01
mybatis-plus 自定義 Service Vo接口實現(xiàn)數(shù)據(jù)庫實體與 vo
這篇文章主要介紹了mybatis-plus 自定義 Service Vo接口實現(xiàn)數(shù)據(jù)庫實體與 vo 對象轉換返回功能,本文通過實例圖文相結合給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-08-08
Spring Cache監(jiān)控配置與使用規(guī)范的建議
這篇文章主要介紹了Spring Cache監(jiān)控配置與使用規(guī)范的建議,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
設置JavaScript自動提示-Eclipse/MyEclipse
自動提示需要2個組件,分別是:ext-4.0.2a.jsb2||spket-1.6.16.jar,需要的朋友可以參考下2016-05-05
SpringBootWeb?入門了解?Swagger?的具體使用
這篇文章主要介紹了SpringBootWeb?入門了解?Swagger?的具體使用,Swagger?框架可以根據(jù)已經實現(xiàn)的方法或者類,通過頁面的方式直觀清晰的查看或者進行測試該方法,需要的朋友可以參考下2024-08-08
Mybatis之通用Mapper動態(tài)表名及其原理分析
這篇文章主要介紹了Mybatis之通用Mapper動態(tài)表名及其原理分析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08

