Javers 5.11.2忽略ID仍抛ENTITY_INSTANCE_WITH_NULL_ID异常咨询
我来帮你搞定这个问题——你遇到的核心矛盾很明确:JaVers默认依赖JPA的@Id生成GlobalId来区分实体实例,哪怕你用@DiffIgnore或者EntityDefinition忽略了id属性的比较,JaVers依然需要一个有效标识来识别集合中的元素,尤其是新增的无ID临时实例。
下面针对你的ORM级联保存场景,给出几种可行的解决方案:
方案1:给Leaf指定JaVers专属的标识属性
JaVers允许通过@JaversId注解自定义用于生成GlobalId的属性,不需要和JPA的主键绑定。我们可以给Leaf添加一个临时唯一标识(比如UUID),让JaVers用它来识别实例,同时忽略JPA的id:
@AllArgsConstructor @NoArgsConstructor @Builder @Data @Entity public class Leaf { @DiffIgnore @Id private Long id; // JPA主键,JaVers仅忽略该属性的比较 @JaversId private String tempId; // JaVers用来区分实例的唯一标识 private String color; // 重写builder,自动为无tempId的实例生成UUID public static LeafBuilder builder() { return new LeafBuilder() { @Override public Leaf build() { if (super.tempId == null) { super.tempId = UUID.randomUUID().toString(); } return super.build(); } }; } }
修改后,你原来的测试用例不需要改动,新增的Leaf.builder().color("12").build()会自动生成tempId,JaVers能正常识别这是一个新增元素,不会再抛出null id异常。
方案2:将Leaf标记为值对象(ValueObject)
如果Leaf没有独立的业务生命周期,完全依赖于Tree(符合值对象的定义),可以把它注册为JaVers的ValueObject。值对象不需要ID,JaVers会通过属性值来判断实例是否相同:
首先修改Leaf类(可以保留JPA的@Entity和@Id,因为JaVers的ValueObject标识和JPA实体不冲突):
@AllArgsConstructor @NoArgsConstructor @Builder @Data @Entity public class Leaf { @DiffIgnore @Id private Long id; private String color; }
然后在构建Javers实例时,将Leaf注册为ValueObject:
Javers javers = JaversBuilder.javers() .registerValueObject(ValueObjectDefinitionBuilder.valueObjectDefinition(Leaf.class).build()) .build();
⚠️ 注意:值对象的比较是基于属性相等的,如果两个Leaf的color相同,JaVers会认为它们是同一个实例。如果你的业务场景允许这种判断,这个方案最简洁。
方案3:自定义GlobalId生成器
如果以上两种方案都不适合你的业务,可以实现JaVers的GlobalIdProvider接口,完全自定义Leaf的GlobalId生成逻辑:
public class LeafGlobalIdProvider implements GlobalIdProvider { @Override public GlobalId createId(Object domainObject, Javers javers) { if (domainObject instanceof Leaf) { Leaf leaf = (Leaf) domainObject; // 自定义标识规则:有id用id,无id则用color+临时UUID确保唯一 String identifier = leaf.getId() != null ? leaf.getId().toString() : leaf.getColor() + "_" + UUID.randomUUID(); return InstanceIdDTO.instanceId(identifier, Leaf.class).createGlobalId(); } // 其他类沿用JaVers默认的生成逻辑 return javers.getTypeMapping(domainObject.getClass()).createId(domainObject); } }
然后注册这个自定义Provider:
Javers javers = JaversBuilder.javers() .registerGlobalIdProvider(new LeafGlobalIdProvider()) .build();
为什么之前的配置无效?
你之前用@DiffIgnore或EntityDefinition.withIgnoredProperties("id"),只是告诉JaVers不要比较id属性的变化,但并没有改变JaVers识别实体实例的规则——它依然默认使用JPA的@Id作为GlobalId的来源。当新增的Leaf实例id为null时,JaVers无法生成唯一的GlobalId,自然会抛出ENTITY_INSTANCE_WITH_NULL_ID异常。
内容的提问来源于stack exchange,提问作者Kavau

