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.