Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@BatchMapping's api with JPA forces you to use spring.jpa.open-in-view=true #579

Open
rburgst opened this issue Dec 14, 2022 · 3 comments
Open
Labels
status: waiting-for-triage An issue we've not yet triaged

Comments

@rburgst
Copy link

rburgst commented Dec 14, 2022

When you have a spring-data-jpa project with

spring.jpa.open-in-view=false

and an 1:n relationship that you want to traverse via @BatchMapping.

Lets assume the following data model

  • Club
    • Athletes

where 1 club has n athletes

and the following mapping

@Entity
open class Club(
    @Column
    open var clubName: String,
    @Id
    open val id: Tsid? = TsidCreator.getTsid1024(),
)

@Entity
open class Athlete(
    @ManyToOne(fetch = FetchType.LAZY)
    open var club: Club?,
    @Column
    open var lastName: String,
    @Column
    open var firstName: String,
    @Id
    open var id: Tsid? = TsidCreator.getTsid1024(),
)

Then trying to allow quering the club.athletes via Graphql @BatchMapping causes the list of clubs passed in as a parameter to be from a different hibernate session than the athletes loaded in the current session.

    @BatchMapping(typeName = "Club")
    fun athletes(clubs: List<Club>): Map<Club, List<Athlete>> {
        val clubIdList = clubs.map { it.id!! }.toList()
        val athletes = athleteRepository.findAthletesByClubIdIn(clubIdList)
        val result = athletes
            .groupBy { it.club!! }
        return result
    }

Therefore, the athlete.club will return a hibernate proxy which is not initialized at this point.

Either you make the @BatchMapping method @Transactional which causes the athlete.club instances to be hydrated again (therefore, defeating the purpose of the @BatchMapping as this is effectively an n+1 on the club table) or you simply get an exception that the hashcode of the athlete.club cannot be calculated.

I feel that the API for @BatchMapping is a bit unfortunate, it would have been better to allow

    fun athletes(clubs: List<Club>): Map<Tsid, List<Athlete>>

that way, you could use "projection DTO" interfaces (for the Athlete) which would avoid the hibernate lazy loading issue.

A sample repo showing this problem can be found at https://github.com/rburgst/spring-boot3-graphql-kotlin

See also spring-io/initializr#1359

@rburgst rburgst changed the title @BatchMappings api with JPA forces you to use spring.jpa.open-in-view=true` @BatchMapping's api with JPA forces you to use spring.jpa.open-in-view=true Dec 14, 2022
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Dec 14, 2022
@steven-sheehy
Copy link

steven-sheehy commented Jan 2, 2023

I feel that the API for @BatchMapping is a bit unfortunate, it would have been better to allow

fun athletes(clubs: List): Map<Tsid, List>

Also, if we use view models that are auto-generated by a plugin from the GraphQL schema then the we can't add proper equals and hashCode implementations for the key in the map (e.g. a ClubViewModel using the above). This would avoid duplicate lookups across different batch mappings for the same table/entity. One possibility is to use the standard Node interface's id field, if implemented, for the key.

@mp911de
Copy link
Member

mp911de commented Jan 12, 2023

With your model, you have enabled lazy loading. Consequently, if the lazy collection is requested after the transaction, then there's no longer an active session, and you cannot lazy load the remaining data. Switching to eager loading removes the issue. If you still want to stick to lazy loading, then spring.jpa.open-in-view=true is the way to go.

@rburgst
Copy link
Author

rburgst commented Jan 12, 2023

well if the API was only using the ID types then it would be trivial to "fix" the problem. Eager loading has many performance implications, therefore, an API that either requires you to use eager loading or open-in-view is sub-optimal IMHO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged
Projects
None yet
Development

No branches or pull requests

4 participants