为什么这个 Spring (Kotlin) 多对多关系使用 postgresql 作为数据库崩溃创建的休眠?

Why is this Spring (Kotlin) many to many relation created with hibernate using postgresql as db crashing?

我有两个实体:1. Post。 2. 标签。 它们的实现方式如下:

@Entity(name = "Post")
@Table(name = "post")
data class Post(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
var title: String,
@ManytToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
@Fetch(value= FetchMode.SELECT)
@JoinTable(name = "post_tag",
joinColumns = [JoinColumn(name = "post_id")],
inverseJoinColumns = [JoinColumn(name = "tag_id")],)
@JsonIgnoreProperties("posts")
var tags: Set<Tag> = mutableSetOf()
)


@Entity(name = "Tag")
@Table(name = "tag")
data class Tag(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name: String,
@ManyToMany(mappedBy = "tags")
@JsonIgnoreProperties("tags")
var posts: Set<Post> = mutableSetOf(),
)

我可以用数据填充它们,所以我在三个表中都有数据。

但是当我调用 postReository.getAll 时,会抛出以下错误: 警告行超过 500 次,所以它似乎是递归的,之后的错误和下面的 WhosebugError。

有趣的是,当我使用 var tags: List<Tag> = mutableListOf()var posts: List<Post> = mutableListOf(), 而不是 set 时,它起作用了。 但我读到使用集合而不是列表的性能更高。

那么可以使用 set 运行 吗? 或者在 kotlin 中实现这个的最佳实践是什么?

2022-02-01 20:51:03.585  WARN 44603 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts      : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@6cda6288<rs=HikariProxyResultSet@2079611130 wrapping org.postgresql.jdbc.PgResultSet@2f8e2b05>
2022-02-01 20:51:03.585  WARN 44603 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts      : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@2777867f<rs=HikariProxyResultSet@1686136316 wrapping org.postgresql.jdbc.PgResultSet@570a7adb>
2022-02-01 20:51:03.585  WARN 44603 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts      : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@68fb8c90<rs=HikariProxyResultSet@1899379583 wrapping org.postgresql.jdbc.PgResultSet@36f34489>

2022-02-01 20:51:03.592 ERROR 44603 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.WhosebugError] with root cause

java.lang.WhosebugError: null
at java.base/java.nio.ByteBuffer.limit(ByteBuffer.java:1529) ~[na:na]
at java.base/java.nio.MappedByteBuffer.limit(MappedByteBuffer.java:330) ~[na:na]
at java.base/java.nio.MappedByteBuffer.limit(MappedByteBuffer.java:73) ~[na:na]
at java.base/sun.nio.ch.Util$BufferCache.get(Util.java:172) ~[na:na]
at java.base/sun.nio.ch.Util.getTemporaryDirectBuffer(Util.java:232) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.tryWrite(NioSocketImpl.java:394) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.implWrite(NioSocketImpl.java:413) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.write(NioSocketImpl.java:440) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.write(NioSocketImpl.java:826) ~[na:na]
at java.base/java.net.Socket$SocketOutputStream.write(Socket.java:1035) ~[na:na]
at java.base/java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:81) ~[na:na]
at java.base/java.io.BufferedOutputStream.flush(BufferedOutputStream.java:142) ~[na:na]
at org.postgresql.core.PGStream.flush(PGStream.java:665) ~[postgresql-42.2.24.jre7.jar:42.2.24.jre7]
at org.postgresql.core.v3.QueryExecutorImpl.sendSync(QueryExecutorImpl.java:1482) ~[postgresql-42.2.24.jre7.jar:42.2.24.jre7]
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:320) ~[postgresql-42.2.24.jre7.jar:42.2.24.jre7]
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:481) ~[postgresql-42.2.24.jre7.jar:42.2.24.jre7]
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:401) ~[postgresql-42.2.24.jre7.jar:42.2.24.jre7]
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:164) ~[postgresql-42.2.24.jre7.jar:42.2.24.jre7]
at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:114) ~[postgresql-42.2.24.jre7.jar:42.2.24.jre7]
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) ~[HikariCP-4.0.3.jar:na]
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) ~[HikariCP-4.0.3.jar:na]
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.getResultSet(AbstractLoadPlanBasedLoader.java:390) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:163) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:104) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:87) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:710) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:76) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:99) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2163) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.doWork(AbstractPersistentCollection.java:589) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:458) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at com.bla.blabla.model.Post.hashCode(Post.kt) ~[main/:na]
at java.base/java.util.HashMap.hash(HashMap.java:338) ~[na:na]
at java.base/java.util.HashMap.put(HashMap.java:610) ~[na:na]
at java.base/java.util.HashSet.add(HashSet.java:221) ~[na:na]
at java.base/java.util.AbstractCollection.addAll(AbstractCollection.java:336) ~[na:na]
at java.base/java.util.AbstractCollection.addAll(AbstractCollection.java:336) ~[na:na]
at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:355) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:239) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:224) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:198) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl.endLoading(CollectionReferenceInitializerImpl.java:154) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishLoadingCollections(AbstractRowReader.java:253) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:211) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:96) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:105) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:87) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:710) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:76) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:99) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2163) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.doWork(AbstractPersistentCollection.java:589) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:458) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at com.bla.blabla.model.Tag.hashCode(Tag.kt) ~[main/:na]
at java.base/java.util.HashMap.hash(HashMap.java:338) ~[na:na]
at java.base/java.util.HashMap.put(HashMap.java:610) ~[na:na]
at java.base/java.util.HashSet.add(HashSet.java:221) ~[na:na]

向每个 class 主体添加以下覆盖函数就达到了目的:

@Entity(name = "Post")
@Table(name = "post")
data class Post(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
var title: String,
@ManytToMany(cascade = [CascadeType.ALL], fetch = 
FetchType.EAGER)
@Fetch(value= FetchMode.SELECT)
@JoinTable(name = "post_tag",
joinColumns = [JoinColumn(name = "post_id")],
inverseJoinColumns = [JoinColumn(name = "tag_id")],)
@JsonIgnoreProperties("posts")
var tags: Set<Tag> = mutableSetOf()
) {
override fun hashCode(): Int {
    return id.hashCode()
}
}


@Entity(name = "Tag")
@Table(name = "tag")
data class Tag(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name: String,
@ManyToMany(mappedBy = "tags")
@JsonIgnoreProperties("tags")
var posts: Set<Post> = mutableSetOf(),
) {
override fun hashCode(): Int {
    return id.hashCode()
}
}

这里的问题是确定数据 class 实例的 hashCode 由于双向关联(相互引用)导致 Whosebug 错误。

为避免这种情况,您需要从 hashCode 方法中排除 @ManyToMany 属性(例如,通过自己显式声明 hashCode)。