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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:02:12