Spring @ManyToMany 求助:数据库和字段对应后似乎陷入循环引用

2019-03-05 22:16:20 +08:00
 Allianzcortex

Hi 打扰大家了...这个问题理论上不应该很难,毕竟官方和各种文档上都有很多的 demo,但自己在实践过程中还是遇到了一个很尴尬的问题:

场景是一篇文章(Article)有多个标签(Tag)

Article :

public class Article {
    ...

    @ManyToMany(cascade = {CascadeType.ALL})
    @JoinTable(name = "article_tag",
            joinColumns = @JoinColumn(name = "article_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id"))
    
    private Set<Tag> tagList = new HashSet<>();
}

Tag :

public class Tag {
    ...

    @ManyToMany(mappedBy = "tagList")
    private Set<Article> articles=new HashSet<Article>();
}

article 表没有与 tag 有关的字段 tag 表也没有与 article 有关的字段 所有的关系存储在 article_comment 表中:

+------------+---------+------+-----+---------+-------+
| Field      | Type    | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| ID         | int(11) | NO   | PRI | NULL    |       |
| article_id | int(11) | YES  |     | NULL    |       |
| comment_id | int(11) | YES  |     | NULL    |       |
+------------+---------+------+-----+---------+-------+

比如用最简单的语句只是返回一个 Article 对象:

return articleRepository.findBySlug(slug);

在添加 @ManyToMany 字段前是没有问题的,但添加之后就无法查询,会显示以下报错:

Hibernate: select taglist0_.article_id as article_1_2_0_, taglist0_.tag_id as tag_id2_2_0_, tag1_.id as id1_4_1_, tag1_.tag_name as tag_name2_4_1_ from article_tag taglist0_ inner join tag tag1_ on taglist0_.tag_id=tag1_.id where taglist0_.article_id=?
Hibernate: select articles0_.tag_id as tag_id2_2_0_, articles0_.article_id as article_1_2_0_, article1_.id as id1_0_1_, article1_.body as body2_0_1_, article1_.created_at as created_3_0_1_, article1_.description as descript4_0_1_, article1_.favorited as favorite5_0_1_, article1_.favorites_count as favorite6_0_1_, article1_.slug as slug7_0_1_, article1_.title as title8_0_1_, article1_.updated_at as updated_9_0_1_, article1_.user_id as user_id10_0_1_ from article_tag articles0_ inner join article article1_ on articles0_.article_id=article1_.id where articles0_.tag_id=?
Hibernate: select taglist0_.article_id as article_1_2_0_, taglist0_.tag_id as tag_id2_2_0_, tag1_.id as id1_4_1_, tag1_.tag_name as tag_name2_4_1_ from article_tag taglist0_ inner join tag tag1_ on taglist0_.tag_id=tag1_.id where taglist0_.article_id=?

看起来就像是 Article 和 Tag 在互相查询,引用依赖,再之后因为永远在互相查询会引起崩溃,从而:

 HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@4c5c2744<rs=HikariProxyResultSet@353080062 wrapping Result set representing update count of 3>
 Thread starvation or clock leap detected

查询就无法进行下去。

① 对 Article 里的 private Set<Tag> tagList 字段添加 @JsonIgnore 注解,这样返回的结果就不会包含 tag,也就不会报错。

② 修改 getter 方法(之前无论是 lombok 的 @Getter 还是用最经典的 Java getter 方法都不可行),比如重新定义如下:

 public Set<Tag> getTagList() {
        return new HashSet<Tag>();
    }

会返回 "tagList": [], 这个字段,所有都为空。

但这都并不是最终想要的结果 quq

最终目的还是要解决循环依赖,有这样一篇文章: http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html 和主题类似,但里面的方法似乎都仍然没有效果。


所以目前就卡在这个地方,完全没有思路,在网上基本找到所有类似的问题都没有相应解决方法...汗...想请问下 V 站的大家有没有遇到过类似的问题或者有什么调试的思路可以分享下么~谢谢啦。

3419 次点击
所在节点    程序员
15 条回复
qzeng2017
2019-03-05 23:02:40 +08:00
在 Article 类的 tagList 上 @JsonIgnoreProperties(value = {"articles"})
在 Tag 类的 articles 上 @JsonIgnoreProperties(value = {"tagList"})
Allianzcortex
2019-03-05 23:09:09 +08:00
@qzeng2017 谢谢谢谢,这个方法和 http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html 里提到的第三种方法是一样的,遗憾的是还是会报错....
S9Yh4wIFsBG7jnE4
2019-03-05 23:33:57 +08:00
我之前也用过 many to many 的注解 没遇到过这情况啊
Allianzcortex
2019-03-05 23:41:31 +08:00
@shayang888 是啊...看到很多教程也都是开箱即用完全黑箱,但不知道为什么我的就出现这个问题,但我并没有做任何特殊操作(哭泣 !
zjp
2019-03-06 00:21:49 +08:00
上星期正好在写这个
@JsonIgnore 应该不能满足需要,可以用 @JsonManagedReference、 @JsonBackReference,Jackson 会处理好循环引用
用 Lombok 还要重写 equals 和 hashcode 方法,这里也循环引用了。麻烦的是 Tag 不同但其他属性都相同的两个 Article 应不应该是 equals,Tag 同理
Allianzcortex
2019-03-06 00:37:59 +08:00
@zjp 谢谢,这个方法和上面提到的链接里第一种方法是一样的,尝试了但还是不好用,很崩溃
Allianzcortex
2019-03-06 00:57:19 +08:00
卧槽... @zjp 感谢在先,这句 [用 Lombok 还要重写 equals 和 hashcode 方法,这里也循环引用了] 一句惊醒梦中人,已经在小数据集上测试成功了,== 让我重新整理一下
Allianzcortex
2019-03-06 02:34:07 +08:00
@zjp 太感谢了,问题已经解决。我的做法是重写了 @toString() 和 @hashCode() 后不会出现查询的错误,但预期仍然不是我想要的,循环返回 Json, 之后添加 @JsonMangedReference 和 @JsonBackReference 来定义 owner 和 target side,参考: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion 就完全解决了。真的太厉害了,只可惜金币只能加一次!👍👍
airfling
2019-03-06 08:33:13 +08:00
@NamedEntityGraph 这个注解结合 @EntityGraph 注解使用,其次对于多对一和一对一,在一的那一方注解加上属性 @ManyToOne(fetch = FetchType.LAZY)
wc951
2019-03-06 11:56:00 +08:00
多对多关系配置延迟加载,lombok 可以用 @tostring.exclude 排除,@EqualsAndHashCode 也可以设置 exclude,转 json 可以多加一个 dto
mgcnrx11
2019-03-06 11:57:07 +08:00
所以一般在我们那实践里,都不会直接把 Entity 做序列化返回,而是建一个 DTO 类去做返回。这是其中一个用 DTO 的理由
Allianzcortex
2019-03-06 17:37:41 +08:00
@wc951 谢谢😂 一会试一下新配置
Allianzcortex
2019-03-06 17:40:18 +08:00
@mgcnrx11 这是两件事呀~ 我在 @requestbody 这里用的也是 dto,但返回整个类引起循环引用问题不在这儿🤣
zjp
2019-03-06 19:13:47 +08:00
@airfling 既然讨论了我就伸手一回……
在 Spring data JPA 里 fetchType.Lazy 不起作用有可能什么原因?我没对 hibernate 做任何配置…
airfling
2019-03-07 08:25:44 +08:00
@zjp 这个分两种的,一种是你直接用 jpaq 的语法写的方法,这个是 hibernate 的 hsql 查询机制,还有个是用 @query 注解写的 sql,这个是 jpa 自己的机制,这两个的实现机制不一样。你说的这种情况我之前也遇到过,具体解决方法现在忘了,一般解决思路就是把 showsql 打开,查看第一次查询使用的 sql 是否和自己预想的一样。然后多余的 sql 是在查询的时候产生的还是返回数据给前台产生的,返回数据给前台产生的就是检查 tostring 和 equal 方法和 json 序列化检查进行排除。调用产生的就是你用了这个表,但是你没查询出来,使用了 hibernate 的默认机制进行了 n+1 查询

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/541510

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX