You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不使用@Transactional的情况下使用Hibernate @Filter?

问题原因分析

Hibernate的@Filter生效依赖于当前Session的上下文,而Spring Data JPA在非事务环境下的行为是核心原因:

  1. 当服务层没有@Transactional时,Spring Data的findAll方法会临时创建一个Session执行查询,执行完毕后立即关闭。
  2. 你的切面环绕findAll方法执行:在切面中启用过滤器时,使用的是@PersistenceContext注入的EntityManager代理对应的Session,但这个Session和后续findAll内部创建的Session不是同一个实例——非事务环境下EntityManager每次操作都会生成新的Session。所以你看到过滤器已启用,但实际查询用的是另一个无过滤器的Session,自然不生效。
  3. 添加@Transactional后,整个服务方法处于同一个事务上下文内,EntityManager/Session会被复用:切面中启用过滤器的Session和findAll查询用的是同一个Session,过滤器因此生效。
解决办法(无需服务层添加@Transactional)

方案1:在Repository方法上添加只读事务

直接在EmployeeRepository的findAll方法上标注@Transactional(readOnly = true),让Spring Data在Repository层开启只读事务:

@Repository
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, UUID> {
    @Override
    @Transactional(readOnly = true)
    List<Employee> findAll();
}

此时切面环绕findAll时,Session已被事务上下文绑定,启用的过滤器会作用于同一个Session,查询时自动生效。

方案2:修改切面,通过TransactionTemplate管理临时事务

在切面中使用TransactionTemplate手动开启只读事务,确保过滤器和查询共用同一个Session:

@Aspect
@Component
public class TestAspect {

    private final SecurityAspect securityAspect;
    private final Logger logger;
    private final EntityManager entityManager;
    private final TransactionTemplate transactionTemplate;

    @Autowired
    public TestAspect(SecurityAspect securityAspect, Logger logger, EntityManager entityManager, TransactionTemplate transactionTemplate) {
        this.securityAspect = securityAspect;
        this.logger = logger;
        this.entityManager = entityManager;
        this.transactionTemplate = transactionTemplate;
        // 设置为只读事务,优化性能
        transactionTemplate.setReadOnly(true);
    }
    

    @Around("execution(* org.springframework.data.repository.CrudRepository+.findAll(..))")
    public Object aroundFindAllTenantAware(ProceedingJoinPoint joinPoint) {
        return transactionTemplate.execute(status -> {
            Session session = entityManager.unwrap(Session.class);
            session.enableFilter("testFilter")
                    .setParameter("companyId", securityAspect.getCompanyId());
            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                logger.error(throwable.getMessage(), throwable);
                throw new RuntimeException(throwable);
            } finally {
                session.disableFilter("testFilter");
            }
        });
    }
}

这种方式无需修改Repository或服务层,由切面统一管理查询的事务上下文,确保过滤器生效。

方案3:调整EntityManager的作用域(不推荐)

将EntityManager的作用域从默认的事务范围改为请求范围,但这会延长Session生命周期,可能带来性能问题,不建议生产环境使用。

补充说明

你提到教程中无需@Transactional也能生效,大概率是因为那些教程的切面切入点或Session管理逻辑不同——比如切面直接作用于事务内的方法,或者使用了请求范围的EntityManager。而你的场景下,非事务环境下Session无法复用是核心问题。

内容的提问来源于stack exchange,提问作者Vadim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 06:05:25