pokemzok
pokemzok

Reputation: 1709

Kotlin - Data class entity throws StackOverflowError

I try to combine kotlin (version 1.2.21) with spring-boot (1.5.9.RELEASE). I have encountered a problem using data class with @Entity annotation. My problematic entities look like this:

@Entity
@Table(name = "APP_USER")
data class AppUser(

    @Column(name = "USERNAME", unique = true)
    private val username: String,

    @Column(name = "PASSWORD")
    private val password: String,

    @Column(name = "IS_ACTIVE")
    val isActive: Boolean,

    @Column(name = "REGISTRATION_DATE_TIME")
    val registrationDateTime: LocalDateTime = SystemTimeManager.getSystemDateTime(),

    @OneToMany(mappedBy = "appUser", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
    val authorities: MutableSet<UserAuthority> = mutableSetOf()
) : EntityBase(), UserDetails {


   internal fun addRole(authority: UserAuthority) {
      this.authorities.add(authority)
   }

}


@Entity
@Table(name = "USER_AUTHORITY")
data class UserAuthority(
    @ManyToOne
    @JoinColumn(name = "APP_USER_ID", nullable = false)
    val appUser: AppUser,

    @Column(name = "ROLE", length = 50, nullable = false)
    @Enumerated(value = EnumType.STRING)
    private val authority: Authority
) : EntityBase(), GrantedAuthority {

override fun getAuthority(): String {
    return authority.name
    }   
}

As you see, we have here @OneToMany relation between AppUser and UserAuthority. Now I try to add few authorities like this:

authoritiesCollection.forEach { appUser.addRole(UserAuthority(appUser, Authority.valueOf(it))) }

During execution first authority is always correctly added to appUser, however adding second authority produces StackOverflowError with a stacktrace

java.lang.StackOverflowError
at security.usermanagement.AppUser.hashCode(AppUser.kt)
at security.usermanagement.UserAuthority.hashCode(UserAuthority.kt)

If I make these class non data it works as it should. I probably can fix this by overriding hashcode and equals methods however I have many entities so I really rather not.

Upvotes: 9

Views: 2613

Answers (1)

pablobu
pablobu

Reputation: 1311

You have a circular dependency between AppUser and UserAuthority. You need to exclude one to break the circular dependency when processing the hashCode.

You can fix it by moving the properties that cause de circular dependency to the data class body, that way those properties won't be used on the autogenerated fields. In this case, it would be moving authorities to the AppUser body:

@Entity
@Table(name = "APP_USER")
data class AppUser(

        @Column(name = "USERNAME", unique = true)
        private val username: String,

        @Column(name = "PASSWORD")
        private val password: String,

        @Column(name = "IS_ACTIVE")
        val isActive: Boolean,

        @Column(name = "REGISTRATION_DATE_TIME")
        val registrationDateTime: LocalDateTime = SystemTimeManager.getSystemDateTime(),

) {
    @OneToMany(mappedBy = "appUser", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
    val authorities: MutableSet<String> = mutableSetOf()

    internal fun addRole(authority: String) {
        this.authorities.add(authority)
    }

}

Upvotes: 14

Related Questions