使用 @ManyToMany 时出现 StackOverflowError

And*_*dre 2 hibernate jpa h2 kotlin spring-boot

在尝试并谷歌搜索类似问题数小时后,我仍然无法解决问题:

我正在构建一个使用 JPA(因此也是休眠)和 H2(不认为它相关)的冲刺启动应用程序(在 Kotlin 中)。我想对 User 和 Achievement 类之间的多对多关系进行建模(因此一个用户可以拥有多个成就,多个用户可以实现一个成就)。以下是模型类:

@Entity
data class User(

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        val id: Int = 0,

        @Column(nullable = false)
        val name: String,

        @ManyToMany(cascade = [ CascadeType.PERSIST, CascadeType.MERGE ])
        val achievements: MutableSet<Achievement> = HashSet()

)
Run Code Online (Sandbox Code Playgroud)
@Entity
data class Achievement(

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        val id: Int = 0,

        val key: String,

        @ManyToMany(mappedBy = "achievements")
        val users: MutableSet<User> = HashSet()

)
Run Code Online (Sandbox Code Playgroud)

当我看到以下日志时,此模型似乎工作正常:

Hibernate: create table achievement (id integer not null, key varchar(255), priority integer not null, category_id integer, primary key (id))
Hibernate: create table user (id integer not null, name varchar(255) not null, primary key (id))
Hibernate: create table user_achievements (users_id integer not null, achievements_id integer not null, primary key (users_id, achievements_id))
Run Code Online (Sandbox Code Playgroud)

然后我在 @Service @Transactional 中预填充数据,如下所示:

val achievement = Achievement(key = "SOME_ACHIEVEMENT_KEY")
achievementRepository.save(achievement)

val user = User(name = "John Doe")
userRepository.save(user)
Run Code Online (Sandbox Code Playgroud)

并添加一个关系:

val foundAchievement = achievementRepository.findById(achievementId)
val foundUser = userRepository.findById(userId)

foundAchievement.ifPresent { achievement ->
    foundUser.ifPresent { user ->
        user.achievements.add(achievement)
        userRepository.save(user)
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我现在尝试通过对以下 URL 之一执行 GET 来访问数据:

http://localhost:8080/achievements/2/users
http://localhost:8080/users/3/achievements
Run Code Online (Sandbox Code Playgroud)

我得到一个 java.lang.StackOverflowError 并且日志显示 hibernate 正在尝试一遍又一遍地查询成就和用户(无限循环直到堆栈溢出)。

所以这里有我的问题:

  1. 你知道为什么会出现这个错误吗?
  2. 我的模型正确吗?
  3. 我将用户连接到成就的方式是否正确,或者我是否还需要将用户添加到成就.用户(甚至将整个内容保存在成就库中)?

And*_*dre 5

好的,问题是 Kotlin 的双向关系和自动生成的 equals() 和 hashCode() 的组合。

由于我使用的是 Kotlin 数据类,它会自动为主构造函数中定义的每个属性生成 hashCode() 和 equals()。我从主构造函数中取出了 @ManyToMany 注释属性,问题消失了:

@Entity
data class Achievement(

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        val id: Int = 0,

        val key: String

) {

    @ManyToMany(mappedBy = "achievements")
    val users: MutableSet<User> = HashSet()

}
Run Code Online (Sandbox Code Playgroud)
@Entity
data class User(

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        val id: Int = 0,

        @Column(nullable = false)
        val name: String

) {

    @ManyToMany(cascade = [CascadeType.PERSIST, CascadeType.MERGE])
    val achievements: MutableSet<Achievement> = HashSet()

}
Run Code Online (Sandbox Code Playgroud)