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 Book
s.
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 Book
s, 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.