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

如何通过Jackson JSON和Hibernate实现递归属性的完全反序列化?

问题:Jackson序列化N-N关联及自引用实体时,重复对象仅返回ID的解决办法

我使用SpringBoot 2.3.9搭配Hibernate,两张表为N-N关系,已完成单向映射。ObjectB包含自引用列,通过Jackson进行递归序列化时,仅首次出现的setObjectB属性被完整序列化,后续仅显示ID,需要所有条目都返回完整POJO。

实体映射代码

ObjectA 实体

@Entity
@Table(name = "TABLE_OBJECT_A")
public class ObjectA {

    // 映射N-N关系
    @JoinTable(name = "OBJECTA_X_OBJECTB",
            joinColumns = @JoinColumn(name = "A_ID"),
            inverseJoinColumns = @JoinColumn(name = "B_ID")
    )
    private Set<ObjectB> setObjectB = new HashSet<ObjectB>();

}

ObjectB 实体

@Entity
@Table(name = "TABLE_OBJECT_B")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
// 设置为false是因为需要完整对象
@JsonIdentityReference(alwaysAsId = false)
public class ObjectB {

    // 自引用字段
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "B_RECURSION_ID")
    private ObjectB objectBRecursion;

}

JPA findAll的JSON响应

[
  {
    "version": "2023-05-09T16:39:47.082+00:00",
    "id": 1619,
    "code": "TRSU",
    "setObjectB": [
      {
        "id": 31,
        "version": "2018-07-18T08:12:14.000+00:00",
        "code": "AMO"
      },
      {
        "id": 100,
        "version": "2023-04-18T10:00:45.786+00:00",
        "code": "SMO"
      }
    ]
  },
  {
    "version": "2023-05-09T16:33:25.404+00:00",
    "id": 1520,
    "codice": "SD",
    "setObjectB": [
      100
    ]
  },
  {
    "version": "2023-05-09T16:40:48.313+00:00",
    "id": 1620,
    "code": "TRSE",
    "setObjectB": [
      31,
      100
    ]
  },
  {
    "version": "2022-09-26T08:00:04.127+00:00",
    "id": 1019,
    "code": "test",
    "setObjectB": [
      31
    ]
  },
  {
    "version": "2023-04-18T09:50:27.744+00:00",
    "id": 1519,
    "code": "DI",
    "setObjectB": [
      31
    ]
  }
]

根据Jackson的JsonIdentityInfo文档说明:

In practice this is done by serializing the first instance as full object and object identity, and other references to the object as reference values.

需实现所有条目返回完整POJO,以下是可行解决办法:


解决办法

方法1:移除@JsonIdentityInfo注解

@JsonIdentityInfo的核心作用是处理循环引用时用ID替代重复对象。如果你的业务场景不存在循环引用风险(比如自引用不会形成闭环),直接删除ObjectB上的@JsonIdentityInfo和@JsonIdentityReference注解即可,Jackson会每次都序列化完整对象。

注意:若自引用存在循环(如某个对象的objectBRecursion指向自身或形成闭环),会触发StackOverflowError。

方法2:使用DTO层转换

避免直接序列化JPA实体,定义对应DTO类,在服务层将实体属性拷贝到DTO后再序列化,完全控制输出结构。

示例DTO定义:

public class ObjectADTO {
    private Long id;
    private String version;
    private String code;
    private Set<ObjectBDTO> setObjectB;
    // Getter、Setter及属性拷贝方法
}

public class ObjectBDTO {
    private Long id;
    private String version;
    private String code;
    private ObjectBDTO objectBRecursion;
    // Getter、Setter及属性拷贝方法
}

在Service层查询实体后,使用MapStruct、ModelMapper等工具将实体拷贝到DTO,返回DTO给前端。这种方式隔离了实体层与表现层,灵活性最高。

方法3:自定义Jackson序列化器

为ObjectB编写自定义序列化器,强制每次序列化完整对象,忽略Jackson的身份识别机制。

示例序列化器:

public class ObjectBSerializer extends StdSerializer<ObjectB> {

    public ObjectBSerializer() {
        super(ObjectB.class);
    }

    @Override
    public void serialize(ObjectB value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();
        gen.writeNumberField("id", value.getId());
        gen.writeStringField("version", value.getVersion().toString());
        gen.writeStringField("code", value.getCode());
        if (value.getObjectBRecursion() != null) {
            gen.writeObjectField("objectBRecursion", value.getObjectBRecursion());
        }
        gen.writeEndObject();
    }
}

在ObjectB实体上指定该序列化器:

@Entity
@Table(name = "TABLE_OBJECT_B")
@JsonSerialize(using = ObjectBSerializer.class)
public class ObjectB {
    // 原有代码
}

注意:后续实体属性变更时需同步修改序列化器,维护成本较高。

方法4:通过Jackson配置禁用身份识别

不修改实体注解,通过配置ObjectMapper覆盖ObjectB的身份识别注解:

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        // 使用MixIn覆盖ObjectB的@JsonIdentityInfo注解
        mapper.addMixIn(ObjectB.class, ObjectBMixIn.class);
        return mapper;
    }

    // 空的MixIn类,不添加@JsonIdentityInfo注解
    abstract class ObjectBMixIn {}
}

这种方式既保留其他Jackson注解的功能,又针对性禁用了ObjectB的身份识别逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 14:36:49