Reputation: 7995
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
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
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
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
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
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
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
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