Spring Boot JPA - OneToMany relationship causes infinite loop
Solution 1
Problem solved. I was using a custom @toString
method in the LinkedAccount which was referencing the ParentAccount. I had no idea that this could cause any problem and therefor I did not include the toString in my question.
Apparently, this was causing an infinite loop of lazy loading and removing this reference fixed the problem.
Solution 2
As the first answer suggests:
Do not use Lombok's
@Data
annotation on@Entity
classes.
Reason: @Data
generates hashcode()
, equals()
and toString()
methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString()
and it crashes.
Smajl
Software developer with focus on Java and cloud technologies.
Updated on July 27, 2022Comments
-
Smajl almost 2 years
I have a two objects with simple @OneToMany relationship which looks as follows:
parent:
@Entity public class ParentAccount { @Id @GeneratedValue private long id; private String name; @OneToMany(fetch = FetchType.EAGER, mappedBy = "parentAccount") private Set<LinkedAccount> linkedAccounts; }
child:
@Entity public class LinkedAccount { @Id @GeneratedValue private long id; @ManyToOne(optional = false) private ParentAccount parentAccount; private String name; // empty constructor for JPA public LinkedAccount() { } }
I ma using Spring
CrudRepository
to operate with these entities. However, when callingParentAccount parent = parentAccountRepository.findOne(id);
, some kind of infinite loop starts happening and hibernate spams this all over the console:Hibernate: select linkedacco0_.parent_account_id as parent_a6_1_0_, linkedacco0_.id as id1_0_0_, linkedacco0_.id as id1_0_1_, linkedacco0_.aws_id as aws_id2_0_1_, linkedacco0_.key_id as key_id3_0_1_, linkedacco0_.name as name4_0_1_, linkedacco0_.parent_account_id as parent_a6_0_1_, linkedacco0_.secret_key as secret_k5_0_1_ from linked_account linkedacco0_ where linkedacco0_.parent_account_id=?
I tried changed the fetch type to LAZY but then I get this error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.berrycloud.scheduler.model.ParentAccount.linkedAccounts, could not initialize proxy - no Session
(It seems that it is trying to do the lazy load outside of the transactional context).
This is my CRUD repository:
@Repository public interface ParentAccountRepository extends CrudRepository<ParentAccount, Long> { }
Could someone tell me how to resolve this issue? I would prefer the solution with EAGER fetch. Thank you for any tips
EDIT: here is the schema I am using
CREATE TABLE parent_account ( id BIGINT auto_increment, name VARCHAR(80) null, PRIMARY KEY (`id`) ); CREATE TABLE linked_account ( id BIGINT auto_increment, parent_account_id BIGINT, name VARCHAR(80) null, FOREIGN KEY (`parent_account_id`) REFERENCES `parent_account` (`id`), PRIMARY KEY (`id`) );
-
Smajl over 8 yearsI would have to edit my sql schema (which already exists) to add the manager_id column. Also, spring has its way of doing this automatically so I would like to use the deafault Spring JPA way of resolving this issue
-
Mejmo over 8 yearsWhat is non spring jpa there? By the way, your code works for me, you have something in your database data wrong. Check it twice.
-
Smajl over 8 yearsI edited my question and added the schema I am using. Maybe there is something wrong with that?
-
Smajl over 8 yearsAlso: @JoinColumn(name = "parent_account_id") on the parentAccount reference did not do anything (I think that Springs injects this automatically so it is already there)
-
Mejmo over 8 yearsI would really like to help you, but I am unable to reproduce. I have the same schema, two entries in linked accounts, one in parent, and I have no loops while querying.
-
Lev over 7 yearsLombok was indeed the cause of an infinite loop on in-memory model creation and eventually of the StackOverflowError in my case. Nevertheless, I find Lombok's getters and setters generation too useful to give up on it entirely. Hence, I only disabled Lombok generating toString() by adding my own toString() method to a class that just returned super.toString() - and it solved my issue.
-
Ena about 6 yearsYou can still use Lombok excluding your lazy fetch fields.
@EqualsAndHashCode(exclude={"firstLazyField","secondLazyField"})
and@ToString(exclude={"firstLazyField","secondLazyField"})
-
Sida Zhou over 4 yearsThis is also nice: stackoverflow.com/a/47118403/2202107
-
Yoeri Van Nieuwerburg over 3 yearsAs @Ena stated, you can indeed use the exclusions. Mind that in lombok you could also use
@EqualsAndHashcode.Include
on field level. If you do this, DO NOT forget to either remove the class-level@EqualsAndHashcode
or tell lombok to only include the explicitly included fields by setting@EqualsAndHashCode(onlyExplicitlyIncluded = true)
on class level. P.S.: instead of the exclude list on@ToString
, you could also add@ToString.Exclude
on a field