I have 2 services: RecentRecordService and BookService.
@Service
public class RecentRecordService {
    @Transactional
    public List<Book> getRecentReadBooks() {
        List<Long> recentReadBookIds = getRecentReadBookIds();
        List<Book> recentReadBooks = new ArrayList<>();
        for (Long bookId : recentReadBookIds) {
            try {
                Book book = bookService.getBook(bookId);
                recentReadBooks.add(book);
            } catch (AccessDeniedException e) {
                // skip
            }
        }
        return recentReadBooks;
    }
}
@Service
public class BookService {
    @Transactional
    public Book getBook(Long bookId) {
        Book book = bookDao.get(bookId);
        if (!hasReadPermission(book)) {
            throw new AccessDeniedException(); // spring-security exception
        }
        return book;
    }
}
Assume that getRecentReadBookIds() returns [1, 2, 3].
When the session user has permission for all the book IDs that returned from getRecentReadBookIds(), everything works fine. getRecentReadBooks() will return a list of 3 Books.
Suddenly, the owner of book #2 changed the permission setting of it from "Public" to "Private". Therefore, the session user can no longer read the book #2.
I was expecting that getRecentReadBooks() will return a list of 2 Books, which contains the info of book #1 and book #3. However, the method failed with the following exception:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
After some research, I found that it has something to do with the transaction propagation. The transation will be marked as "rollback-only" even if I use try-catch to handle the AccessDeniedException in getRecentReadBooks().
This problem seems to be solved by changing the @Transactional annotation for getBook() into:
    @Transactional(propagation = Propagation.NESTED)
    public Book getBook(Long bookId) {
        // ...
    }
or
    @Transactional(noRollbackFor = AccessDeniedException.class)
    public Book getBook(Long bookId) {
        // ...
    }
However, I was wondering if I can solve the problem by only modifying RecentRecordService. After all, RecentRecordService is the one who wants to handle AccessDeniedException on its own.
Any suggestion will help. Thanks.