java - 在内部服务中捕获 AccessDeniedException 时防止事务回滚

标签 java spring hibernate spring-transactions

我有 2 个服务:RecentRecordServiceBookService

@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/

相关文章:

java - 尝试运行 Grails 应用程序时出错

java - 变量尚未从内部类访问

java - Spring 数据查找返回类型

java - 如何在线程中注入(inject)Spring Bean

java - hibernate 模式 : how to get the current audited table name?

Java Spring,使用域对象

java - 启动应用程序 spring-boot 后执行类中的某些方法

java - 使用 Maven2 构建现有 EAR

java.lang.NoSuchMethodError : org. springframework.beans.factory.support.DefaultListableBeanFactory.getDependencyComparator()Ljava/util/Comparator;"}}

hibernate - @Procedure 注解孤立数据库连接(spring-data-jpa)