【问题标题】:Spring JPA One To Many Infinite RecursionSpring JPA 一对多无限递归
【发布时间】:2020-04-09 05:35:15
【问题描述】:

我试图返回一个具有@OneToMany@ManyToOne 双向关系的JPA 数据(当然,转换为DTO)。我目前申请的东西fix。问题是输出是可恢复的。 cmets 有帖子有 cmets 然后有帖子(cmets -> 帖子 -> 评论 -> 等等..)。

我只想拥有这样的东西

{
    "post_id": 1
    "user": {
        // user data
    },
    "description": "some description",
    "images": "{images,images}",
    "tags": "{tags, tags}",
    "comments": [
     {
        //some comments data

     },
     {
        //some comments data
     }
    ]
    "lastUpdated": "2020-04-08T14:23:18.000+00:00"
}

这是我的代码

这是我的 Posts.class

@Data
@Entity
@Table(name = "posts")
@EntityListeners(AuditingEntityListener.class)
public class Posts {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "post_id")
    private Long post_id;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private Users user;

    @Column(name = "description")
    private String description;

    @Column(name="images")
    private String images;

    @Column(name="tags")
    private String tags;

    @OneToMany(
        fetch = FetchType.LAZY,
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    @JsonIgnoreProperties(value = { "comments" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
    //@JsonManagedReference
    private List<Comments> comments;


    @LastModifiedDate
    @Column(name = "last_updated")
    private Date lastUpdated;


    public void addComments(Comments comment) {
        this.comments.add(comment);
    }
}

这是我的 PostDTO.class

@Data
public class PostDTO {

    private Long post_id;
    private UserDTO user;
    private String description;
    private String images;
    private String tags;
    private List<CommentsDTO> comments;
    private Date lastUpdated;


}

这是我的 Comments.class

@Data
@Entity
@Table(name = "comments")
@EntityListeners(AuditingEntityListener.class)
public class Comments {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="comment_id")
    private Long comment_id;

    @OneToOne
    @JoinColumn(name = "user_id")
    private Users user;

    @Column(name = "description")
    private String description;

    @Column(name="images")
    private String images;

    @ManyToOne
    @JoinColumn(name ="post_id" , nullable = false)
    @JsonIgnoreProperties(value = { "post" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
    //@JsonBackReference
    private Posts post;

    @Column(name="last_updated")
    @LastModifiedDate
    private Date lastUpdated;

}

这是我的 CommentsDTO.class

@Data
public class CommentsDTO {

    private Long comment_id;

    private UserDTO user;

    private String description;

    private PostDTO post;

    private String images;

    private Date lastUpdated;
}

这是我的 REST 控制器

@GetMapping
public @ResponseBody ResponseEntity<List<PostDTO>>  getAll() throws Exception {

    return new ResponseEntity<List<PostDTO>>(service.getAll(), HttpStatus.OK);
}

这是我的服务

public List<PostDTO> getAll() throws Exception  {
    return repo.findAll()
               .stream()
               .map(this::convert)
               .collect(Collectors.toList());
}

private PostDTO convert(Posts e) {
    return  mapper.map(e, PostDTO.class);
}

希望有人能阐明我的问题。到现在为止有点迷失了。

【问题讨论】:

  • 您需要最高 n 级的数据吗?
  • 对不起,我用我的预期输出更新了帖子
  • 你可以试试@JsonIgnoreProperties(value = { "post" ,"hibernateLazyInitializer", "handler" }, allowSetters = true) private List&lt;Comments&gt; comments;For post 也改一样试试
  • 是一样的:(
  • 你能在代码中更新你所做的吗?

标签: java spring spring-data-jpa jackson


【解决方案1】:

问题是当您将Post 转换为PostDTO 时,然后通过使用ModelMapper,它会为PostDTO 调用Post 的所有字段的getter。所以这递归地发生了 mapper.map(e, PostDTO.class)

所以,只需从 CommentDTO 中删除 private PostDTO post , 然后 modelmapper 不要尝试设置 PostDTO->comment-> post 字段。 而且您不需要 DTO 中的双向关系。 DTO 就是您想要在响应中显示的内容。

【讨论】:

  • 啊,我明白了,它起作用了,但是我如何将它应用于实际实体,因为由于OneToMany 关系,我不能只删除另一个?
  • 您从 DTO 中删除,而不是从实体中删除,因此您的关系仍然存在
  • 是的,我知道,我只是好奇你如何在不删除 Comments 实体上的帖子字段的情况下对实体做同样的事情。因为当我尝试在ResponseEntity 上返回Posts 实体时,会出现相同的回退问题。
  • 为什么你需要这个实体,因为你要为 cmets 实体调用 post getter,当你为惰性调用 gettter 时 JPA 获取数据
  • 当您尝试在 ResponseEntity 上返回 Posts 实体时,由于反序列化调用 Posts 的所有 getter 而发生递归。这就是为什么使用 DTO 进行响应是一个好习惯
【解决方案2】:

您在错误的类上打破了 json 序列化周期。

您正在发送PostDTO 的列表,但将JsonBackReference 应用于CommentsJsonManagedReference 应用于Posts

更新

请注意,ObjectMapper 类、JsonManagedReferenceJsonBackReference 可能来自 2 个包

  • com.fasterxml.jackson.databind.ObjectMapper
  • com.fasterxml.jackson.annotation.JsonManagedReference
  • com.fasterxml.jackson.annotation.JsonBackReference

或:

  • org.codehaus.jackson.map.ObjectMapper
  • org.codehaus.jackson.annotate.JsonManagedReference
  • org.codehaus.jackson.annotate.JsonBackReference

如果不一致,不匹配的注解会被忽略,序列化时仍然会出现死循环。

【讨论】:

  • 我不认为 PostDTO` 有问题,因为我尝试直接将 Posts 发回。两者都有相同的问题。我认为 Back 和 Managed Reference 存在问题,我不知所措。
  • 我都在使用fastxml
  • 另外,我的 dep 树中没有 codehaus 依赖项
  • 您是否尝试过手动序列化:String result = new ObjectMapper().writeValueAsString(posts); 并检查是否重现问题?
  • 是的,在返回 ResponseEntity 之前在我的控制器上尝试了这部分,并通过调试模式确认并用 try-catch 包围它,该行产生相同的问题
【解决方案3】:

将 JsonIgnoreProperties 与 Manage 和反向引用一起使用。

 @Data
    @Entity
    @Table(name = "comments")
    @EntityListeners(AuditingEntityListener.class)
    public class Comments {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name="comment_id")
        private Long comment_id;

        @OneToOne
        @JoinColumn(name = "user_id")
        private Users user;

        @Column(name = "description")
        private String description;

        @Column(name="images")
        private String images;

        @ManyToOne
        @JoinColumn(name ="post_id" , nullable = false)
        @JsonIgnoreProperties(value = { "comments" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
       @JsonBackReference
        private Posts post;

        @Column(name="last_updated")
        @LastModifiedDate
        private Date lastUpdated;

    }

你的 Post 类应该是

@Data
@Entity
@Table(name = "posts")
@EntityListeners(AuditingEntityListener.class)
public class Posts {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "post_id")
    private Long post_id;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private Users user;

    @Column(name = "description")
    private String description;

    @Column(name="images")
    private String images;

    @Column(name="tags")
    private String tags;

    @OneToMany(
        fetch = FetchType.LAZY,
        mappedBy = "post",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    @JsonIgnoreProperties(value = { "post" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
    @JsonManagedReference
    private List<Comments> comments;


    @LastModifiedDate
    @Column(name = "last_updated")
    private Date lastUpdated;


    public void addComments(Comments comment) {
        this.comments.add(comment);
    }
}

另外我建议使用 Getter 和 Setter 注释而不是 @Data 。因为 Tostring() 方法也会导致递归。

【讨论】:

    猜你喜欢
    • 2019-07-19
    • 1970-01-01
    • 1970-01-01
    • 2020-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多