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

Javers 5.11.2忽略ID仍抛ENTITY_INSTANCE_WITH_NULL_ID异常咨询

解决JaVers在ORM级联保存场景下的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 08:03:14