You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

仅修改RecentRecordService解决捕获AccessDeniedException时事务回滚问题

Solution: Fix Transaction Rollback Issue Without Modifying BookService

Great question! The root problem here is that with default transaction propagation (Propagation.REQUIRED), BookService.getBook() runs within the same transaction as RecentRecordService.getRecentReadBooks(). When getBook() throws an AccessDeniedException, Spring marks the entire transaction as rollback-only. Even if you catch the exception in getRecentReadBooks(), the transaction can't commit later, leading to the UnexpectedRollbackException.

Here are two reliable ways to resolve this by only modifying RecentRecordService:

1. Wrap BookService Calls in a New Transaction

Create a helper method in RecentRecordService that calls BookService.getBook() with Propagation.REQUIRES_NEW—this forces each book lookup to run in its own independent transaction. If one fails, only that sub-transaction rolls back, leaving the main transaction intact.

@Service
public class RecentRecordService {
    private final BookService bookService;

    // Constructor injection
    public RecentRecordService(BookService bookService) {
        this.bookService = bookService;
    }

    @Transactional
    public List<Book> getRecentReadBooks() {
        List<Long> recentReadBookIds = getRecentReadBookIds();
        List<Book> recentReadBooks = new ArrayList<>();
        for (Long bookId : recentReadBookIds) {
            try {
                // Call the helper method with its own transaction
                Book book = getBookInIsolatedTransaction(bookId);
                recentReadBooks.add(book);
            } catch (AccessDeniedException e) {
                // Skip unauthorized books as intended
            }
        }
        return recentReadBooks;
    }

    // This method runs in a new, independent transaction
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    protected Book getBookInIsolatedTransaction(Long bookId) {
        return bookService.getBook(bookId);
    }
}

Why this works:

  • Propagation.REQUIRES_NEW starts a brand new transaction for each call to getBookInIsolatedTransaction().
  • When BookService.getBook() throws an exception, only this new transaction is rolled back. The main transaction (for getRecentReadBooks()) remains unmarked for rollback and can commit successfully with the authorized books.

2. Use TransactionTemplate for Isolated Calls

If you prefer not to add a helper method, you can use TransactionTemplate to execute each BookService.getBook() call in a separate transaction. This achieves the same isolation as the first approach but uses programmatic transaction management instead of declarative.

@Service
public class RecentRecordService {
    private final BookService bookService;
    private final TransactionTemplate transactionTemplate;

    // Constructor injection
    public RecentRecordService(BookService bookService, TransactionTemplate transactionTemplate) {
        this.bookService = bookService;
        // Configure the template to use REQUIRES_NEW propagation
        this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    }

    @Transactional
    public List<Book> getRecentReadBooks() {
        List<Long> recentReadBookIds = getRecentReadBookIds();
        List<Book> recentReadBooks = new ArrayList<>();
        for (Long bookId : recentReadBookIds) {
            try {
                Book book = transactionTemplate.execute(status -> {
                    return bookService.getBook(bookId);
                });
                recentReadBooks.add(book);
            } catch (AccessDeniedException e) {
                // Skip unauthorized books
            }
        }
        return recentReadBooks;
    }
}

Why this works:

  • The TransactionTemplate is configured to use REQUIRES_NEW, so each execute() call runs in a separate transaction.
  • Exceptions from getBook() only affect their own transaction, leaving the main transaction able to commit with valid books.

Key Takeaway

Both approaches isolate each book lookup into its own transaction, preventing a single unauthorized book from marking the entire main transaction as rollback-only. This lets you keep BookService unchanged while handling the exception gracefully in RecentRecordService.

内容的提问来源于stack exchange,提问作者johnlinp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:09:41