我有 2 个服务:RecentRecordService
和 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;
}
}
假设 getRecentReadBookIds()
返回 [1, 2, 3]
。
当 session 用户拥有从 getRecentReadBookIds()
返回的所有图书 ID 的权限时,一切正常。 getRecentReadBooks()
将返回 3 个 Book
的列表。
突然,#2 书的所有者将其权限设置从“公开”更改为“私有(private)”。因此, session 用户无法再阅读第 2 本书。
我原以为 getRecentReadBooks()
会返回 2 个 Book
的列表,其中包含第 1 本书和第 3 本书的信息。但是,该方法失败并出现以下异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
经过一些研究,我发现它与事务传播有关。即使我使用 try-catch 处理 getRecentReadBooks()
中的 AccessDeniedException
,事务也会被标记为“仅回滚”。
这个问题似乎可以通过将 getBook()
的 @Transactional
注释更改为:
@Transactional(propagation = Propagation.NESTED)
public Book getBook(Long bookId) {
// ...
}
或
@Transactional(noRollbackFor = AccessDeniedException.class)
public Book getBook(Long bookId) {
// ...
}
但是,我想知道只修改RecentRecordService
是否可以解决问题。毕竟,RecentRecordService
是想要自行处理 AccessDeniedException
的服务。
任何建议都会有所帮助。谢谢。
最佳答案
我认为只修改RecentRecordService
是不可能解决这个问题的,因为交易已经在BookService
中被标记为回滚了 RuntimeException
发生在其中。一旦 TransactionStatus
被标记为 rollback ,就没有办法将其恢复为当前不回滚。
因此,您必须通过以下方式使 BookService#getBook()
不将事务标记为回滚:
@Transactional(noRollbackFor = Throwable.class)
public Book getBook(Long bookId) {
}
这意味着无论发生什么异常,事务都不会被标记为回滚。这样做是有意义的,因为这个方法应该是只读的,所以当一个方法不应该修改任何东西时回滚是没有意义的。
关于java - 在内部服务中捕获 AccessDeniedException 时防止事务回滚,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59412942/