StackOverflowError при использовании @ManyToMany - PullRequest
0 голосов
/ 06 января 2019

после того, как несколько часов пытался найти похожую проблему, я все равно не смог ее решить:

Я создаю загрузочное приложение для спринта (в Kotlin), которое использует JPA (так что также спящий) и H2 (не думаю, что это актуально). Я хочу смоделировать отношения «многие ко многим» между классами «Пользователь» и «Достижение» (чтобы у пользователя было несколько достижений, а достижение может быть достигнуто несколькими пользователями). Вот классы моделей:

@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()

)
@Entity
data class Achievement(

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

        val key: String,

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

)

Эта модель работает нормально, так как я вижу следующие журналы:

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))

Затем я предварительно заполняю данные в @Service @Transactional следующим образом:

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

val user = User(name = "John Doe")
userRepository.save(user)

и добавить отношение:

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

foundAchievement.ifPresent { achievement ->
    foundUser.ifPresent { user ->
        user.achievements.add(achievement)
        userRepository.save(user)
    }
}

Если я сейчас попытаюсь получить доступ к данным, выполнив GET по одному из следующих URL:

http://localhost:8080/achievements/2/users
http://localhost:8080/users/3/achievements

Я получаю java.lang.StackOverflowError, и журналы показывают, что hibernate снова и снова пытается запрашивать достижения и пользователей (бесконечный цикл до переполнения стека).

Итак, вот мои вопросы:

  1. У вас есть идея, почему возникает эта ошибка?
  2. Верна ли моя модель?
  3. Является ли мой способ подключения пользователя к достижению правильным или мне нужно также добавить пользователя в достижение.users (или даже сохранить все это в репозитории достижений)?

1 Ответ

0 голосов
/ 06 января 2019

Хорошо, проблема заключалась в комбинации двунаправленного отношения и автоматически генерируемых equals () и hashCode () от Kotlin.

Поскольку я использовал класс данных 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()

}
@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()

}
...