Spring-data-JDBC使用应用生成ID时执行save更新报错如何解决
问题根因
Spring Data JDBC 对实体执行插入/更新的逻辑判断,早于BeforeSave回调的触发时机:
- 首次调用
save方法时:实体ID初始为null,框架判定为新实体,走插入逻辑,之后才触发BeforeSave回调生成并赋值ID,最终插入数据库成功。 - 二次调用
save执行更新时:实体已提前携带ID,框架判定为存量实体走更新逻辑,默认的状态判断规则不匹配应用侧生成ID的场景,就会抛出ID不存在的更新语义异常。
解决方案
方案1:实体实现Persistable接口自定义状态判断(最推荐)
自行控制实体是否为新实例的判断逻辑,适配应用生成ID的场景:
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PostLoad; import org.springframework.data.annotation.Transient; import org.springframework.data.domain.Persistable; public class DomainObjectLongId implements Persistable<Long> { @Id private Long id; // 其余业务字段省略 // transient 标记字段不参与持久化 @Transient private boolean isNew; @Override public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public boolean isNew() { return isNew; } // 新建实体、生成ID时调用该方法标记为新实例 public void markAsNew() { this.isNew = true; } // 从数据库查询实体后自动标记为存量实例 @PostLoad private void markAsNotNew() { this.isNew = false; } }
你原有BeforeSave回调中生成ID的逻辑无需修改,只要在生成ID后调用markAsNew()方法即可。
方案2:通过@Version乐观锁字段自动判断
如果你的业务本身需要乐观锁能力,直接添加@Version注解字段即可自动解决状态判断问题:
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; import java.util.UUID; public class DomainObjectUuid { @Id private UUID id; // 新增乐观锁字段,新建实体时该字段为null,框架自动判定为新实例 @Version private Integer version; // 其余业务字段省略 }
插入操作完成后框架会自动给version字段赋值为1,后续更新操作自动走更新逻辑,同时自带乐观锁能力。
方案3:全局自定义IsNewStrategy
如果项目全量实体都采用应用侧生成ID的模式,可以全局配置判断规则,无需每个实体单独适配:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.mapping.model.PersistentEntityIsNewStrategy; @Configuration public class JdbcCustomConfig { @Bean public JdbcMappingContext jdbcMappingContext() { JdbcMappingContext mappingContext = new JdbcMappingContext(); mappingContext.setIsNewStrategyFactory(entity -> PersistentEntityIsNewStrategy.of(entity, idProperty -> { // 替换为你项目通用的新实例判断逻辑即可 Object id = entity.getPropertyAccessor(entity).getProperty(idProperty); return id == null; }) ); return mappingContext; } }
内容的提问来源于stack exchange,提问作者DashingSuprHero
相关产品推荐
相关产品推荐

