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

Spring JPA更新异常:无法获取实体及唯一约束未拦截后续执行

问题分析与解决方案

问题1原因

  • deviceDao.save()操作的是Hibernate持久化上下文(Session)中的实体对象,修改后保存会同步更新上下文内的缓存。后续findById查询时,Hibernate优先从上下文缓存读取数据,因此能拿到更新后的实体。
  • 自定义的updateAsset()方法使用@Modifying注解的JPQL语句,属于直接执行数据库SQL,完全绕过了Hibernate的持久化上下文。此时上下文内的Device实体仍保持旧状态,后续查询会直接读取缓存里的旧数据,无法获取更新后的值。默认情况下@Modifying不会自动清除上下文缓存,必须手动触发刷新或配置clearAutomatically = true。

问题2原因

Spring事务默认的异常回滚规则是:仅当事务方法内抛出**未检查异常(RuntimeException及其子类)**时才触发回滚,但这里的约束违例异常存在延迟抛出的情况:

  • 使用@Modifying的JPQL更新操作,Hibernate默认会将DML语句缓存,直到事务提交、执行查询操作触发flush,或者手动调用flush时才会真正执行SQL。
  • 当updateClass.doSth()执行完更新后,SQL并未立即执行,事务继续走到messagePublisher.doSth()。此时findById查询触发Hibernate flush缓存中的DML操作,这才抛出ConstraintViolationException。但此时messagePublisher的代码已经执行完毕(因为查询先从上下文缓存拿到了修改后的实体),最终事务因异常回滚,但消息已经发送。

问题2的替代解决办法(除提前查询外)

  • 手动触发Flush:在updateAsset()方法执行后,立即调用deviceDao.flush(),强制Hibernate执行SQL,让约束违例异常在updateClass.doSth()阶段就抛出,终止后续代码执行:
    // UpdateClass.doRealThing中修改
    deviceDao.updateAsset(assetId, deviceId);
    deviceDao.flush(); // 新增手动flush
    
  • 配置@Modifying自动清除缓存:在DAO的updateAsset()方法上添加clearAutomatically = true,确保上下文缓存与数据库同步:
    @Modifying(clearAutomatically = true)
    @Query("UPDATE device a SET a.asset = ?1 WHERE a.device = ?2")
    int updateAsset(UUID asset, UUID device);
    
  • 捕获异常并终止流程:在updateClass.doRealThing()中捕获DataIntegrityViolationException(Spring对Hibernate约束违例的包装类),抛出异常中断后续执行:
    protected void doRealThing(UUID deviceId, UUID assetId) {
        Optional<Device> device = deviceDao.findById(deviceId);
        if (device.isPresent()) {
            try {
                deviceDao.updateAsset(assetId, deviceId);
                deviceDao.flush();
            } catch (DataIntegrityViolationException e) {
                throw new RuntimeException("资产ID重复,更新失败", e);
            }
        }
    }
    
  • 使用EntityManager手动控制:注入EntityManager,执行更新后调用entityManager.flush()和entityManager.clear(),强制同步数据库状态并清除上下文缓存。

相关代码

ExampleDeviceDao

public interface ExampleDeviceDao extends JpaRepository<Device, UUID>, JpaSpecificationExecutor<Device> {

    @Modifying
    @Query("UPDATE device a SET a.asset = ?1 WHERE a.device = ?2")
    int updateAsset(UUID asset, UUID device);

}

公共服务类Service

@Component
public class Service {
    @Autowired
    UpdateClass updateClass;
    @Autowired
    MiddleClass middleClass;
    @Autowired
    MessagePublisher messagePublisher;

    @org.springframework.transaction.annotation.Transactional
    public void updateAsset(UUID deviceId, UUID assetId) {
        updateClass.doSth(deviceId, assetId);
        middleClass.doSth(deviceId, assetId);
        messagePublisher.doSth(deviceId, assetId);
    }
}

AbstractClass

public abstract class AbstractClass {

    protected abstract void doRealThing(UUID deviceId, UUID assetId);

    public void doSth(UUID deviceId, UUID assetId) {
        doRealThing(deviceId, assetId);
    }
}

UpdateClass

@Component
public class UpdateClass extends AbstractClass{

    @Autowired
    ExampleDeviceDao deviceDao;

    protected void doRealThing(UUID deviceId, UUID assetId) {
        Optional<Device> device = deviceDao.findById(deviceId);
        if (device.isPresent()) {
            device.get().setAsset(assetId);
            /** approach 1:*/
            deviceDao.save(device.get());
            /**
             * approach 2:
             * deviceDao.updateAsset(assetId, deviceId);
             */
        }
    }
}

MiddleClass

@Component
public class MiddleClass extends AbstractClass{

    protected void doRealThing(UUID deviceId, UUID assetId) {
        //do other things, not db update or query.
    }
}

MessagePublisher

@Component
public class MessagePublisher extends AbstractClass{

    @Autowired
    ExampleDeviceDao deviceDao;
    @Autowired
    SNSMessagePublisher snsMessagePublisher;

    protected void doRealThing(UUID deviceId, UUID assetId) {
        Optional<Device> device = deviceDao.findById(deviceId);
        if (device.isPresent()) {
            snsMessagePublisher.publishMessage(device.get());
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 22:09:37