Hibernate @OneToMany关系导致JSON结果中的无限循环或空条目

28

我有两个实体,一个叫做“电影”,另一个叫做“剪辑”。每个剪辑属于一个电影,一个电影可以拥有多个剪辑。

我的代码如下:

Movie.java
    @OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Clip> clips = new HashSet<Clip>();


  
 Clip.java
    
    @ManyToOne
        @JoinColumn(name="movie_id")
        private Movie movie;
正在生成,每个都有一个“movie_id”列,但这会导致我请求数据时陷入无限循环
    @Path("/{id:[0-9][0-9]*}")
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public Movie lookupMovieById(@PathParam("id") long id) {
            return em.find(Movie.class, id);
        }
    

result:
{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[{"id":1,"version":1,"name":"MGS Walkthrough P1","keywords":null,"movie":{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[{"id":1,"version":1,"name":"MGS Walkthrough P1","keywords":null,"movie":{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[{"id":1,"version":1,"name":"M...

当我请求一个剪辑时,得到的结果与请求一个电影时相同。

当我将其改为@ManyToMany关系时,就不会出现这样的问题,但这不是我此处所需的。你能帮我吗?将fetchType设置为Lazy并没有起作用。

编辑:我正在使用当前的JBoss开发工具

编辑:

我通过阅读这篇文章来“解决”这个问题:

https://web.archive.org/web/20180401022123/http://blog.jonasbandi.net/2009/02/help-needed-mapping-bidirectional-list.html

“要映射双向一对多,其中一对多面作为所有者端,必须删除mappedBy元素,并将多对一@JoinColumn设置为不可插入和不可更新。这个解决方案显然并不优化,并将产生一些额外的UPDATE语句。”

当我请求一个电影时,得到以下答复:

{"id":1,"version":1,"name":"MGS Walkthrough","filename":"video.mp4","movieCategories":[{"id":1,"version":1,"name":"Walkthrough"}],"clips":[],"description":"Trailer zu mgs4"}

“clips”条目仍然出现。这仍然是错误的解决方案,还是我只能接受这个结果?


链接无法使用。 - CodeSlave
@CodeSlave 我更新了博客的存档链接... https://web.archive.org/web/20180401022123/http://blog.jonasbandi.net/2009/02/help-needed-mapping-bidirectional-list.html - Lama
9个回答

68

解决方案:

对于第一个被实例化的对象,请使用@JsonManagedReference注释。

对于第二个被实例化的对象,请使用@JsonBackReference注释。

Movie.java

@JsonManagedReference
@OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Clip> clips = new HashSet<Clip>();

Clip.java

@JsonBackReference
@ManyToOne
    @JoinColumn(name="movie_id")
    private Movie movie;

1
优秀的解决方案,这正是应该采取的方式,不像其他一些粗糙的方法。 - patryk
这是正确的解决方案。每个人都应该首先尝试这个解决方案。这个问题困扰了我,我无法解决它。放弃了几个月。再次尝试,找到了这个答案,它起作用了! - mljohns89
1
这是正确的答案,其他的都是变通方法。 - Sérgio Thiago Mendonça
1
使用JsonManagedReference,FetchType.Eager还是Lazy比较好? - Murtaza Khursheed Hussain
这些注释对我不可用。能否解释一下它们来自哪里? - Jasper de Vries
这很有用。 - Lina Hammami

22

我遇到了完全相同的问题。 我尝试了引用段落中的解决方案,但对我没有用。

我的解决办法是在Clip类中为getMovie()返回null,然后无限循环问题就不再存在了。以JSON格式返回的数据如下:{"movieId":1 ... clips:["clipId":1, "movie":"null", ..]}。

如果您还想在JSON中进一步删除电影属性,请在Clip类中添加类级别注释@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)。

Jackson功能:防止序列化空值、默认值

更新: 我发现更简单的方法是简单地删除Clip类中的getter方法。


我不太明白这个的意义,如果我们不需要返回set/list或JSON,为什么要做所有这些复杂的关系映射?只是为了更新连接表吗? - John Smith
1
@JohnSmith,自从我在这里发布我的答案以来已经有一段时间了,但我记得那是一个不允许更改模式的遗留系统。 - Dino Tw
谢谢。这真的帮了我很多。 - Srikar
谢谢兄弟。这个问题让我疯了。 - Richard
使用Jackson 2.x,您可以使用@JsonInclude(JsonInclude.Include.NON_NULL)。 - Omid Ashouri
如果我以后需要getter方法怎么办? - Shiva kumar

9
首先,让我向您展示为什么将获取类型设置为lazy并没有帮助。 当您尝试序列化您的pojo时,您的序列化程序(可能是jackson)会调用此pojo的每个getter,并使用getter返回的值作为json数据中的属性。因此,它显式地调用getter,这会调用hibernate来加载与之关联的实体(Clip的电影和Movie的剪辑)。 因此,您需要使用@JsonIgnoreProperties来摆脱这种奇怪的无限循环,代码如下:
Clip.java

    @ManyToOne
    @JoinColumn(name="movie_id")
    @JsonIgnoreProperties("clips")
    private Movie movie;

Movie.java
    @OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JsonIgnoreProperties("movie")
    private Set<Clip> clips = new HashSet<Clip>();

那样的话,您会发现嵌套在剪辑JSON对象中的电影没有"clips",而嵌套在电影中的片段也没有"movie"子项。
我想这是处理此问题的最佳方式,也是开发Java Web应用程序的最佳实践。

1
我一直在使用@JsonIgnoreProperties但没起作用。改为使用@JsonIgnoreProperties("clips"),现在正常运行了。非常感谢!!:D - Object Manipulator
1
这个和移除 Lombok 的 @Data 对我有用。 - Hugo Feijó
这是最好的答案,救了我的一天:)) - Hamid

2

需要在子类中添加@JsonIgnore以避免出现此异常。请注意不要将此注释添加到父类中。

@Entity  
@Table(name="Movie")
public class Movie implements Serializable{

@OneToMany(mappedBy="movie",targetEntity=Clip.class,cascade=CascadeType.ALL,
fetch=FetchType.EAGER)
private Set<Clip> clips = new HashSet<Clip>();

}

@Entity  
@Table(name="Clip")
public class Clip implements Serializable{   
    @ManyToOne
    @JoinColumn(name="movie_id")
    @JsonIgnore
    private Movie movie;
}

2
我理解这个情况的主要问题是:当您获取电影时,系统会加载一系列相关片段的列表。但在片段类中,您有一个属性“Movie”,当您调用此属性的getter方法时,系统会再次加载电影。
翻译后的内容如下:

我理解这个情况的主要问题是:当您获取电影时,系统会加载一系列相关片段的列表。但在片段类中,您有一个属性“Movie”,当您调用此属性的getter方法时,系统会再次加载电影。

Movie.java
    @OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Clip> clips = new HashSet<Clip>();

//the below getter will look for data of related Clip
    public Set<Clip> getClips(){ return this.clips}


 Clip.java

    @ManyToOne
        @JoinColumn(name="movie_id")
        private Movie movie;

//the below getter will look for related Movie again
   public Movie getMovie() { return this.movie }


例如: 您获得了电影01,这部电影与剪辑01和剪辑02有关联。当系统加载电影01的数据时,它也通过您的获取方法提取剪辑01和剪辑02的数据。
但是,在剪辑类中,您还拥有属性Movie和一个getter方法getMovie()。因此,当系统查找Clip 01的数据时,它也会获取关联电影的数据,在这种情况下是Movie 01...而Movie 01将获取CLip 01的数据。所以这正是我们出现循环的原因。
因此,解决这种情况的确切方法是删除Clip.java中的Getter Method getMovie(),因为我们不需要使用这些信息。

2

您说“入口剪辑仍然出现”。

为了避免数据库响应中的关系数据更改,请将fetch = FetchType.EAGER更改为fetch = FetchType.Lazy


2

建议返回一个DTO对象,而不是Entity对象,只包含你需要的数据。你可以直接从Hibernate查询/准则结果中获取一个DTO对象,使用结果转换器。


1
基本上,JsonIgnore或JsonBackrefrencing将从一端删除链接。
为了进行正确的链接以及正确的Json输出,请按照以下代码片段进行操作:
@Entity  
@Table(name="Movie")
public class Movie{

@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="@id")
@OneToMany(mappedBy="movie",cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Set<Clip> clips = new HashSet<Clip>();

}


@Entity  
@Table(name="Clip")
public class Clip{ 

@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="@id")  
@ManyToOne
@JoinColumn(name="movie_id")
@JsonIgnore
private Movie movie;
}

请参考以下链接获取更多详细信息:https://www.toptal.com/javascript/bidirectional-relationship-in-json
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

0

根据Dino Tw的说法,有一个简单的方法:

Clip类中移除getMovie()函数。

这将帮助您检索每个MovieClip列表,这些列表通过Clip实体/表中的movie_id关联。


这不是一个新的答案,请将此文本作为评论添加到其他答案中。 - Ignacio Ara

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接