I am currently implementing GraphQL into a system and am using Spring Boot 3.0.5 together with org.springframework.boot:spring-boot-starter-graphql
to implement GraphQL schemas and queries. However there is a query that has issues.
The relevant JPA entities:
@Entity
public class Video {
@Id
@Column(length = 24, nullable = false)
private String videoId;
@ManyToMany(mappedBy = "video")
private Set<Program> programs = new HashSet<>();
@PreRemove
private void removeProgramAssociation() {
if (programs != null) {
for (Program program : this.programs) {
program.getVideo().remove(this);
}
}
}
}
@Entity
public class Program extends Auditable {
@Id
@Column(length = 50)
private String programId;
@ManyToMany(fetch = FetchType.EAGER,
cascade = {
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST})
@JoinTable(
name = "video_program",
joinColumns = @JoinColumn(name = "programId"),
inverseJoinColumns = @JoinColumn(name = "videoId")
)
@ToString.Exclude
@EqualsAndHashCode.Exclude
private Set<Video> video;
}
When executing the following query we receive a org.hibernate.LazyInitializationException
because program can not be loaded directly.
query videoMedia {
video {
materialId
programs {
programId
}
}
}
I am aware that setting FetchType.EAGER for Video.program is a possible solution to avoid the exception, however I would rather explore other options before giving in. The only other question or link I was able to find was https://stackoverflow.com/a/48046402/3681158 which unfortunately seems to no longer be valid in Spring Boot 3.0.5 due to class name changes.
I have finally found a solution to avoid FetchType.EAGER.
In the GraphQL controller I add a method with @SchemaMapping which allows me to create custom mappings on defined fields within a GraphQL type. This fulfills all my requirements since the @SchemaMapping annotated mapper method will not be called if the GraphQL client does not request any programs in the response.
/*
* This annotation allows me to create a custom maping.
* The typename is the typename that you find in the graphql schema,
* whereas the field is the field name you wish to apply it on.
*/
@SchemaMapping(typeName = "VideoMedia", field = "programs")
public Set<Program> programs(VideoMedia videoMedia) {
return programService.findAllProgramsByVideoMedia(videoMedia);
}