Spring事务问题:切面执行时事务未提交,需切面在事务提交后执行
解决Spring Boot事务提交后执行邮件切面的问题
嗨,我碰到过一模一样的场景!你的切面现在在事务边界内执行,核心原因是:你拦截的是Repository层的save*方法,而Spring Data JPA的事务(不管是Repository默认的还是外层Service的@Transactional),提交动作是在调用Repository的外层方法执行完成后才触发的。当你在切面里调用joinPoint.proceed(),只是跑完了Repository的方法逻辑,但事务还处于未提交状态,这时候发邮件就会出现“邮件发了但数据可能回滚”的问题。
下面给你两种最实用的解决方案:
方案1:用TransactionSynchronizationManager注册提交回调(快速修改)
直接在现有切面里添加事务同步逻辑,让邮件代码在事务提交后执行,同时修复原切面返回值丢失的问题(原切面返回void会导致调用save的地方拿不到实体返回值,这是个隐藏bug):
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; @Aspect @Component public class EmailAspect { private final EmailSender emailSender; public EmailAspect(EmailSender emailSender) { this.emailSender = emailSender; } @Around("execution(* com.company.app.service.*Repository.save*(..))") public Object sendEmailAfterCommit(ProceedingJoinPoint joinPoint) throws Throwable { // 先执行Repository的save/update逻辑 Object updatedEntity = joinPoint.proceed(); // 判断当前是否有活跃事务,有的话注册提交后回调 if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { // 这里的代码会在事务提交成功后执行 String subject = String.format("实体已%s", joinPoint.getSignature().getName()); String body = String.format("更新后数据:%s", updatedEntity.toString()); emailSender.send(subject, body, "test@test.com"); } }); } else { // 如果没有事务(比如手动调用Repository),直接发送邮件 emailSender.send("实体操作通知", "无事务环境下执行操作", "test@test.com"); } // 必须返回Repository方法的结果,否则上层调用会丢失返回值 return updatedEntity; } }
方案2:用@TransactionalEventListener实现解耦(更优雅)
如果想把切面和邮件逻辑彻底解耦,可以用Spring的事件驱动机制,在事务提交后触发邮件发送:
步骤1:定义自定义实体变更事件
import org.springframework.context.ApplicationEvent; public class EntityChangedEvent<T> extends ApplicationEvent { private final T oldEntity; private final T newEntity; private final String action; // save/update/delete public EntityChangedEvent(Object source, T oldEntity, T newEntity, String action) { super(source); this.oldEntity = oldEntity; this.newEntity = newEntity; this.action = action; } // 生成getter方法 public T getOldEntity() { return oldEntity; } public T getNewEntity() { return newEntity; } public String getAction() { return action; } }
步骤2:修改切面发布事件(需要注入ApplicationEventPublisher)
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import jakarta.persistence.EntityManager; @Aspect @Component public class EntityChangeEventAspect { private final ApplicationEventPublisher eventPublisher; private final EntityManager entityManager; public EntityChangeEventAspect(ApplicationEventPublisher eventPublisher, EntityManager entityManager) { this.eventPublisher = eventPublisher; this.entityManager = entityManager; } @Around("execution(* com.company.app.service.*Repository.save*(..))") public Object publishEntityChangedEvent(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); Object oldEntity = null; // 尝试获取旧数据(仅更新场景有效,新增场景oldEntity为null) if (args.length > 0 && args[0] != null) { Object entity = args[0]; Object entityId = entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(entity); if (entityId != null) { oldEntity = entityManager.find(entity.getClass(), entityId); } } Object newEntity = joinPoint.proceed(); // 发布事件,事务提交后才会触发监听 eventPublisher.publishEvent(new EntityChangedEvent<>(this, oldEntity, newEntity, "save")); return newEntity; } }
步骤3:创建事务提交后的邮件监听器
import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.event.TransactionPhase; import org.springframework.stereotype.Component; @Component public class EntityChangedEmailListener { private final EmailSender emailSender; public EntityChangedEmailListener(EmailSender emailSender) { this.emailSender = emailSender; } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void sendEmailOnEntityChange(EntityChangedEvent<?> event) { String entityType = event.getNewEntity().getClass().getSimpleName(); String subject = String.format("%s实体已%s", entityType, event.getAction()); String body = String.format("旧数据:%s\n新数据:%s", event.getOldEntity() != null ? event.getOldEntity().toString() : "无", event.getNewEntity().toString()); emailSender.send(subject, body, "test@test.com"); } }
这种方式的好处是完全解耦了切面逻辑和邮件发送逻辑,后续要修改通知方式(比如改成短信),只需要新增监听器即可。
内容的提问来源于stack exchange,提问作者Xeshan J
相关产品推荐
相关产品推荐

