4

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.

codeme
  • 861
  • 2
  • 12
  • 29
  • Possible duplicate of [Infinite Recursion with Jackson JSON and Hibernate JPA issue](https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue) – Alan Hay Nov 07 '18 at 17:57

2 Answers2

13

Please notice that this answer is solution for Kotlin data classes with ManyToMany bidirectional relation.

@ManyToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
@JoinTable(name = "ach",
           joinColumns = [JoinColumn(name = "route_id", referencedColumnName = "uid")],
           inverseJoinColumns = [JoinColumn(name = "athlete_id", referencedColumnName = "uid")])
@JsonIgnoreProperties("routes")
var athletes: List<Athlete> = mutableListOf())


@ManyToMany(mappedBy = "athletes")
@JsonIgnoreProperties("athletes")
var routes: List<Route> = mutableListOf())

With adding the @JsonIgnoreProperties, you can avoid the recursive loop.

codeme
  • 861
  • 2
  • 12
  • 29
3

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.

T_Nova
  • 51
  • 2
  • Your answer is gold, thank you for solving it!!! – Vincent Jul 05 '22 at 11:17
  • This is partially why it is not recommended to use data classes as entities. See more in this article: https://www.baeldung.com/kotlin/jpa#data-classes – Nuno Feb 20 '23 at 10:56