Spring Boot JPA - OneToMany relationship causes infinite loop

17,152

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.

Share:
17,152
Smajl
Author by

Smajl

Software developer with focus on Java and cloud technologies.

Updated on July 27, 2022

Comments

  • Smajl
    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 calling ParentAccount 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
    Smajl over 8 years
    I 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
    Mejmo over 8 years
    What 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
    Smajl over 8 years
    I edited my question and added the schema I am using. Maybe there is something wrong with that?
  • Smajl
    Smajl over 8 years
    Also: @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
    Mejmo over 8 years
    I 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
    Lev over 7 years
    Lombok 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
    Ena about 6 years
    You can still use Lombok excluding your lazy fetch fields. @EqualsAndHashCode(exclude={"firstLazyField","secondLazyFiel‌​d"}) and @ToString(exclude={"firstLazyField","secondLazyField"})
  • Sida Zhou
    Sida Zhou over 4 years
  • Yoeri Van Nieuwerburg
    Yoeri Van Nieuwerburg over 3 years
    As @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