如何用AOP结合JPA实现Hibernate Filter软删除功能?
问题描述
我想用切面实现软删除功能,直接在Service中结合Hibernate Filter和JPA时功能正常,但改用AOP切面后,切面能正常执行,但Filter的条件没有加入到查询中。
正常工作的Service代码
@Service @AllArgsConstructor public class BlogPostServiceImpl implements BlogPostService { private BlogPostRepository blogPostRepository; private BlogPostResponseMapper blogPostResponseMapper; @PersistenceContext public EntityManager entityManager; @Transactional @Override public BlogPostResponse getBlogPostById(UUID id) { Session session = entityManager.unwrap(Session.class); session.enableFilter(SOFT_DELETE_FILTER); BlogPost blogPost = blogPostRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Blog post not found")); session.disableFilter(SOFT_DELETE_FILTER); return blogPostResponseMapper.modelToDto(blogPost); } }
使用AOP后的Service代码
@Service @AllArgsConstructor public class BlogPostServiceImpl implements BlogPostService { private BlogPostRepository blogPostRepository; private BlogPostResponseMapper blogPostResponseMapper; @PersistenceContext public EntityManager entityManager; @Transactional @Override public BlogPostResponse getBlogPostById(UUID id) { BlogPost blogPost = blogPostRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Blog post not found")); return blogPostResponseMapper.modelToDto(blogPost); } }
切面代码
@Slf4j @Aspect @Component @Order(0) public class SoftDeleteAspect { @Before("execution(* com.sample.cms.service..*.*(..))") public void beforeExecution(JoinPoint joinPoint) throws Throwable { Class<?> classAspect = joinPoint.getTarget().getClass(); Field[] fields = classAspect.getDeclaredFields(); for (Field field : fields) { if (StringUtils.equals("entityManager", field.getName())) { field.setAccessible(true); EntityManager entityManager = (EntityManager) field.get(joinPoint.getTarget()); Session session = entityManager.unwrap(Session.class); session.enableFilter(SOFT_DELETE_FILTER); } } } @After("execution(* com.sample.cms.service..*.*(..))") public void afterExecution(JoinPoint joinPoint) throws Throwable { Class<?> classAspect = joinPoint.getTarget().getClass(); Field[] fields = classAspect.getDeclaredFields(); for (Field field : fields) { if (StringUtils.equals("entityManager", field.getName())) { field.setAccessible(true); EntityManager entityManager = (EntityManager) field.get(joinPoint.getTarget()); Session session = entityManager.unwrap(Session.class); session.disableFilter(SOFT_DELETE_FILTER); } } } }
解决方法
问题核心在于事务与AOP的执行顺序不匹配,以及Session与事务的绑定时机错误,以下是具体修复方案:
1. 改用@Around切面控制Filter生命周期
@Before和@After无法正确包裹事务执行流程,因为@Transactional切面优先级默认高于自定义切面,导致Filter启用逻辑在事务开启前执行,此时获取的EntityManager未和当前事务绑定,后续查询用的是事务内的新Session,Filter自然不生效。换成@Around可以明确控制Filter在事务执行期间生效:
@Slf4j @Aspect @Component @Order(0) public class SoftDeleteAspect { @Around("execution(* com.sample.cms.service..*.*(..))") public Object aroundExecution(ProceedingJoinPoint joinPoint) throws Throwable { EntityManager entityManager = getEntityManagerFromTarget(joinPoint.getTarget()); if (entityManager == null) { return joinPoint.proceed(); } Session session = entityManager.unwrap(Session.class); try { session.enableFilter(SOFT_DELETE_FILTER); // 执行目标方法,此时事务已开启,Session与事务绑定 return joinPoint.proceed(); } finally { // 确保Filter最终被禁用 session.disableFilter(SOFT_DELETE_FILTER); } } private EntityManager getEntityManagerFromTarget(Object target) throws IllegalAccessException { Class<?> targetClass = target.getClass(); for (Field field : targetClass.getDeclaredFields()) { if (EntityManager.class.isAssignableFrom(field.getType())) { field.setAccessible(true); return (EntityManager) field.get(target); } } return null; } }
2. 确保切面覆盖事务代理范围
如果@Transactional注解在接口方法上,Spring默认的JDK动态代理只会代理接口方法,可能导致切面不生效。可以在配置类或切面类上添加@EnableAspectJAutoProxy(proxyTargetClass = true),强制使用CGLIB类代理,避免接口代理的局限。
3. 验证Filter的实体配置
确保实体类上正确配置了Hibernate Filter,且启用时设置必要参数:
@Entity @FilterDef(name = SOFT_DELETE_FILTER, parameters = @ParamDef(name = "isDeleted", type = Boolean.class)) @Filter(name = SOFT_DELETE_FILTER, condition = "is_deleted = :isDeleted") public class BlogPost { // ... 其他字段 private Boolean isDeleted = false; }
启用Filter时补充参数设置:
session.enableFilter(SOFT_DELETE_FILTER).setParameter("isDeleted", false);
4. 检查事务传播行为
如果目标方法内部调用了其他事务传播行为为REQUIRES_NEW的方法,需要确保切面覆盖这些方法,或者在新事务中重新启用Filter。
内容的提问来源于stack exchange,提问作者Mohammad Fatoni
相关产品推荐
相关产品推荐

