Spring @Transactional场景下,如何判断事务已回滚并获取自定义错误码?
Hey there! Let's work through how to handle your Spring transaction scenario for each Affiliate—including tracking rollbacks and retrieving custom error codes. Here's a step-by-step solution tailored to your needs:
1. Isolate Single-Affiliate Logic with a New Transaction per Entity
To ensure each Affiliate gets its own independent transaction, we'll extract the persistence/update logic into a separate method with the REQUIRES_NEW transaction propagation. This tells Spring to start a fresh transaction for every call to this method, so failures in one won't affect others.
2. Create a Custom Business Exception for Error Codes
We'll define a runtime exception that carries your custom error code. Spring automatically rolls back transactions when runtime exceptions are thrown, so this lets us signal failure and pass along your specific error code at the same time.
3. Iterate Through Affiliates and Handle Rollbacks
In your main traversal method, call the single-Affiliate processing method in a try/catch block. Catching the custom exception will tell you that the transaction for that Affiliate rolled back, and you can extract the error code directly from the exception.
Step 1: Custom Exception with Error Code
public class AffiliateProcessingException extends RuntimeException { private final int errorCode; public AffiliateProcessingException(int errorCode, String message) { super(message); this.errorCode = errorCode; } public AffiliateProcessingException(int errorCode, String message, Throwable cause) { super(message, cause); this.errorCode = errorCode; } public int getErrorCode() { return errorCode; } }
Step 2: Service Layer with Transactional Methods
@Service public class AffiliateService { private final EntityManager em; private final Logger logger = LoggerFactory.getLogger(AffiliateService.class); // Constructor injection for EntityManager public AffiliateService(EntityManager em) { this.em = em; } // Main traversal method: no transaction needed here public void processAllAffiliates(List<Affiliate> affiliates) { for (Affiliate affiliate : affiliates) { try { processSingleAffiliate(affiliate); logger.info("Successfully processed Affiliate ID: {}", affiliate.getId()); } catch (AffiliateProcessingException e) { // This catch block means the transaction for this Affiliate rolled back logger.error("Failed to process Affiliate ID: {} | Error Code: {}", affiliate.getId(), e.getErrorCode(), e); // Add logic here to handle failed affiliates (e.g., log to a failure table) } } } // Single-Affiliate processing: independent transaction per call @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void processSingleAffiliate(Affiliate affiliate) { logger.info("Starting DB operations for Affiliate ID: {}", affiliate.getId()); long startTime = System.nanoTime(); try { // 1. Persist Payment Payment payment = affiliate.getPayment(); em.persist(payment); // 2. Persist Payment Details List<PaymentPostingDetail> detailsToInsert = affiliate.getPaymentPostingDetails(); for (PaymentPostingDetail ppd : detailsToInsert) { em.persist(ppd); } // 3. Update Postings List<Posting> postingsToUpdate = affiliate.getPostings(); for (Posting p : postingsToUpdate) { if (p.getSignature() != null) { p.getSignature().setModification("withholding-tax.pay", new Date()); } else { String errorMsg = String.format("Posting ID %s has no PersistenceSignature!", p.getIdentifier()); logger.error(errorMsg); // Throw custom exception to trigger rollback and pass error code throw new AffiliateProcessingException( JobStatus.ABNORMAL_END_OF_EXECUTION_ERROR.getCode(), errorMsg ); } em.merge(p); } logger.info("Completed processing Affiliate ID {} in {} ns", affiliate.getId(), System.nanoTime() - startTime); } catch (Exception e) { logger.error("Unexpected error processing Affiliate ID: {}", affiliate.getId(), e); // Wrap any unexpected exceptions in our custom error code exception throw new AffiliateProcessingException( JobStatus.ABNORMAL_END_OF_EXECUTION_ERROR.getCode(), "Failed to process affiliate data", e ); } } }
REQUIRES_NEWPropagation: This ensures every call toprocessSingleAffiliatestarts a brand-new transaction. If one fails, only that transaction rolls back—others continue unaffected.- Automatic Transaction Management: Spring handles
begin()androllback()for you. If the method completes without exceptions, Spring commits the transaction; if our custom runtime exception is thrown, it rolls back automatically. - Error Code Handling: By catching
AffiliateProcessingExceptionin the traversal loop, you know exactly which Affiliate's transaction failed, and you can access the custom error code directly from the exception object.
内容的提问来源于stack exchange,提问作者Grechka Vassili

