I am currently coding a project, which requires me to map in between two entities: An account and a member. An account can have multiple members, whilst the member can only have one account.
After I’ve finished coding the bidirectional way, everything was working and I got the response I wanted, which is the members only.
From here on the problems started:
I started coding my way to “recipe”. Recipe is connected in a ManyToOne relationship to “house”, which then has a 1:1 relationship to “account”. After implementing this, I’ve discovered a StackOverFlow, which previously wasn’t there. (The one between Account and Member)
Below is an image of said structure in the database. Recipe -> House -> Account.
(The relationships and tables, with blue crosses on top exist, but aren’t really connected)
My entities look like this:
package com.myhome.api.components.account.entity; import com.fasterxml.jackson.annotation.JsonBackReference; import com.myhome.api.components.house.entity.House; import com.myhome.api.components.member.entity.Member; import com.myhome.api.components.recipe.entity.Recipe; import lombok.Data; import javax.persistence.*; import java.util.List; import java.util.Set; @Table(name = "account") @Entity @Data public class Account { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Integer id; @Column(name = "email") private String email; @Column(name = "password") private String password; @Column(name = "token") private String token; @OneToMany( cascade = {CascadeType.ALL}, orphanRemoval = true, mappedBy = "fkAccountId") @JsonBackReference private Set<Member> members; }
package com.myhome.api.components.member.entity; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonManagedReference; import com.myhome.api.components.account.entity.Account; import com.myhome.api.components.meal.entity.Meal; import com.myhome.api.components.rating.entity.Rating; import lombok.Data; import javax.persistence.*; import java.util.Set; @Table(name = "member") @Entity @Data public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; @Column(name = "name") private String name; @Column(name = "icon") private Integer icon; @ManyToOne @JoinColumn(name = "fkAccountId", nullable = false) @JsonManagedReference private Account fkAccountId; @OneToMany( cascade = {CascadeType.ALL}, orphanRemoval = true, mappedBy = "fkMemberId") @JsonBackReference private Set<Meal> meals; @OneToMany( cascade = {CascadeType.ALL}, orphanRemoval = true, mappedBy = "fkMemberId") @JsonBackReference private Set<Rating> ratings; }
My questions are:
- Why is this happening, even tho it didn’t happen before I added the connection in between recipe and house?
- How can I fix it?
- What are the causes?
Advertisement
Answer
Why is this happening
This is caused by toString()
method being present in both the classes and forming cyclic dependency. This leads to infinite recursion calls between Account
and Member
‘s toString() methods.
Consider these 2 simple classes. Lombok’s @Data
generates toString method for all fields by default.
class Member { String name; Account account; @Override public String toString() { return "Member{" + "name='" + name + ''' + ", account=" + account.toString() + //Although string concatenation calls toString() on instance, I thought of adding it to call it out '}'; } } class Account { String id; Member member; @Override public String toString() { return "Account{" + "id='" + id + ''' + ", member=" + member.toString() + //Although string concatenation calls toString on instance, I thought of adding it to call it out '}'; } } public static void main(String[] args) { Account account = new Account(); Member member = new Member(); account.id="1"; account.member=member; member.name ="XYZ"; member.account=account; System.out.println(account); }
When you call toString method on one of the classes, say Account
it will in turn call toString
method of Member
and the cycle repeats until you run out of stack memory. Here’s the stacktracee:
Exception in thread "main" java.lang.StackOverflowError at java.lang.StringBuilder.append(StringBuilder.java:136) at com.javainuse.main.Member.toString(AnotherMain.java:23) at java.lang.String.valueOf(String.java:2994) at java.lang.StringBuilder.append(StringBuilder.java:131) at com.javainuse.main.Account.toString(AnotherMain.java:10) at java.lang.String.valueOf(String.java:2994)
How can I fix it?
Exclude toString generation for one of the classes from your lombok and that should work.refer this I would recommend not having toString at all for entity classes.
What are the causes?
I don’t think I understand. Could you elaborate?