简体   繁体   中英

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

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.

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.

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

}

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

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<>();
}

This simple way worked for me. Just use JsonIgnoreProperties .

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

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 for preventing infinite loop

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM