Smajl
Smajl

Reputation: 7995

Spring Boot JPA - OneToMany relationship causes infinite loop

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`)
);

Upvotes: 8

Views: 19822

Answers (7)

Malki Mohamed
Malki Mohamed

Reputation: 1688

This way worked for me without removing @ToSring annotation:

@Entity
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@AllArgsConstructor
@Table(name = "parent_accounts")

public class ParentAccount {
    @JsonIgnoreProperties({"parentAccount"})
    @OneToMany(mappedBy = "parentAccount",
            cascade = CascadeType.ALL,
            orphanRemoval = true)
    private List<LinkedAccount> linkedAcounts;
    // ...
}
@Entity
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@AllArgsConstructor
@Table(name = "linked_accounts")

public class LinkedAccount {
    @JsonIgnoreProperties("linkedAcounts")
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "parentAccount_id")
    private ParentAccount parentAccount;
    // ...
}

PS: In @JsonIgnoreProperties You can also ignore more than one field to prevent an infinite loop

Upvotes: 1

Bibin Chorickanpara
Bibin Chorickanpara

Reputation: 81

This simple way worked for me. Just use JsonIgnoreProperties .

    @JsonIgnoreProperties(value = {"linkedAccounts"})
    @ManyToOne(cascade = { CascadeType.PERSIST})
    @JoinColumn(name = "abc", referencedColumnName = "abc")
    private ParentAccount parentAccount;
    

Upvotes: 0

Zetared
Zetared

Reputation: 110

As user1819111 told, @Data from Lombok is not compatible with @Entity and FetchType=LAZY. I had used Lombok.Data (@Data) and I was getting this error.

As I don't want do create all get/set, I just put the Lombok @Setter and @Getter in your class and all will work fine.

@Setter
@Getter
@Entity
@Table(name = "file")
@SequenceGenerator(name = "File_Sequence", allocationSize=1, sequenceName = "file_id_seq")
public class MyClass{
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "File_Sequence")
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "file", cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
    private Set<Base2FileDetail> details = new HashSet<>();
}

Upvotes: 1

user977221
user977221

Reputation:

I recently had this issue due to a poorly defined Jackson2HttpMessageConverter.

I had done something like the following.

@Bean
RestTemplate restTemplate(@Qualifier("halJacksonHttpMessageConverter")
                                  TypeConstrainedMappingJackson2HttpMessageConverter halConverter) {
    final RestTemplate template = new RestTemplateBuilder().build();
    halConverter.setSupportedMediaTypes(List.of(/* some media types */)); 
    final List<HttpMessageConverter<?>> converters = template.getMessageConverters();
    converters.add(halConverter);
    template.setMessageConverters(converters);
    return template;
}

This caused a problem because the media types did not include all the defaults. Changing it to the following fixed the issue for me.

halConverter.setSupportedMediaTypes(
    new ImmutableList.Builder<MediaType>()
        .addAll(halConverter.getSupportedMediaTypes())
        .add(/* my custom media type */)
        .build()
);

Upvotes: 0

user1819111
user1819111

Reputation: 391

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.

Upvotes: 27

Smajl
Smajl

Reputation: 7995

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.

Upvotes: 7

Mejmo
Mejmo

Reputation: 2593

Something like this does not work?

@Entity
public class Account {

    @Id
    @GeneratedValue
    private long id;
    private String name;

    @ManyToOne(cascade={CascadeType.ALL})
    @JoinColumn(name="manager_id")
    private Account manager;

    @OneToMany((fetch = FetchType.EAGER, mappedBy="manager")
    private Set<Account> linkedAccounts = new HashSet<Account>();

}

Upvotes: 0

Related Questions