Reputation: 364
after trying around and googling for a similar issue for hours, I still could not fix the issue:
I am building a sprint boot application (in Kotlin) that uses JPA (so also hibernate) and H2 (don't think that it's relevant). I want to model a many-to-many relationship between classes User and Achievement (so a user can have multiple achievements and an achievement can be achieved by multiple users). Here are the model classes:
@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()
)
This model seems to work fine as I see following logs:
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))
Then I prepopulate the data in a @Service @Transactional as follows:
val achievement = Achievement(key = "SOME_ACHIEVEMENT_KEY")
achievementRepository.save(achievement)
val user = User(name = "John Doe")
userRepository.save(user)
and add a relation:
val foundAchievement = achievementRepository.findById(achievementId)
val foundUser = userRepository.findById(userId)
foundAchievement.ifPresent { achievement ->
foundUser.ifPresent { user ->
user.achievements.add(achievement)
userRepository.save(user)
}
}
If I now try to access the data by executing GET on one of following URLs:
http://localhost:8080/achievements/2/users
http://localhost:8080/users/3/achievements
I get a java.lang.StackOverflowError and logs show me that hibernate is trying to query for achievements and users over and over again (endless loop until stack overflow).
So here a my questions:
Upvotes: 2
Views: 1325
Reputation: 364
Ok the issue was a combination of bidirectional relation and auto generated equals() and hashCode() by Kotlin.
Since I was using Kotlin data class, it generated hashCode() and equals() automatically for each property defined in the primary constructor. I took the @ManyToMany annotated properties out of the primary constructor the issue dissapeared:
@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()
}
Upvotes: 6