javaspringspring-bootgraphqlspring-graphql

Spring Boot Starter GraphQL LazyInitializationException


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.


Solution

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