如何在不使用@Transactional的情况下使用Hibernate @Filter?
问题原因分析
Hibernate的@Filter生效依赖于当前Session的上下文,而Spring Data JPA在非事务环境下的行为是核心原因:
- 当服务层没有
@Transactional时,Spring Data的findAll方法会临时创建一个Session执行查询,执行完毕后立即关闭。 - 你的切面环绕
findAll方法执行:在切面中启用过滤器时,使用的是@PersistenceContext注入的EntityManager代理对应的Session,但这个Session和后续findAll内部创建的Session不是同一个实例——非事务环境下EntityManager每次操作都会生成新的Session。所以你看到过滤器已启用,但实际查询用的是另一个无过滤器的Session,自然不生效。 - 添加
@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
相关产品推荐
相关产品推荐

