'How to use JPA ManyToMany bidirectional relation with Kotlin

I have two classes which have bidirectional ManyToMany relations in a Spring Boot application. When I would like to fetch my entities, they start recursively looping, and I get a stackoverflow exception. These is my implementation.

@Entity
@Table(name = "route")
data class Route(

        @Column(name = "uid")
        @Type(type = "pg-uuid")
        @Id
        var uid: UUID,
        var image: String,
        @Column(name = "rate_id")
        var rate_id: UUID,
        @ManyToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
        @JoinTable(name = "ach",
                joinColumns = [JoinColumn(name = "route_id", referencedColumnName = "uid")],
                inverseJoinColumns = [JoinColumn(name = "athlete_id", referencedColumnName = "uid")])
        var athletes: List<Athlete> = mutableListOf())

@Entity
@Table(name = "athlete")
data class Athlete(

        @Column(name = "uid")
        @Type(type = "pg-uuid")
        @Id
        var uid: UUID,
        var email: String,
        var image: String,
        @ManyToMany(mappedBy = "athletes")
        var routes: List<Route> = mutableListOf())

I understand that the problem is that both of my list attribute is in the constructor. However I would like to have the list attributes in the response. I have seen solutions where the toString method was overwritten to create a json string. I would prefer to return an object instead of a jsonString. Is there a way to implement the above problem with or without dataclasses? Please give some example if there is a way.



Solution 1:[1]

For me the above solution didn't work. Instead I had to override the equals and hashCode methods to avoid the recursion like so:

@Entity
@Table(name = "route")
data class Route(

        @Column(name = "uid")
        @Type(type = "pg-uuid")
        @Id
        var uid: UUID,
        var image: String,
        @Column(name = "rate_id")
        var rate_id: UUID,
        @ManyToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
        @JoinTable(name = "ach",
                joinColumns = [JoinColumn(name = "route_id", referencedColumnName = "uid")],
                inverseJoinColumns = [JoinColumn(name = "athlete_id", referencedColumnName = "uid")])
        var athletes: List<Athlete> = mutableListOf()) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Route

        if (uid != other.uid) return false
        if (image != other.image) return false
        if (rate_id != other.rate_id) return false

        return true
    }

    override fun hashCode(): Int {
        var result = uid
        result = 31 * result + image.hashCode()
        result = 31 * result + rate_id.hashCode()
        return result
    }
}

By default when you generate the equals and hashCode (by right-clicking > "Generate..." > "equals() and hashCode()" it will look like this:

override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Route

        if (uid != other.uid) return false
        if (image != other.image) return false
        if (rate_id != other.rate_id) return false
        if (route != other.route) return false

        return true
    }

    override fun hashCode(): Int {
        var result = uid
        result = 31 * result + image.hashCode()
        result = 31 * result + rate_id.hashCode()
        result = 31 * result + route.hashCode()
        return result
    }

You have to remove the route object from both methods to stop the recursion. IMPORTANT: You can do this on either side (Athlete or Route) to get the same result.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 T_Nova