Querydsl: fetchJoin(JoinFlag) cancelling

Created on 17 Sep 2020  ·  6Comments  ·  Source: querydsl/querydsl

fetchJoin is useful for traversing entity-graph. But, when projectioning is required, we could not use it.

When projectioning(e.g. min/max/count) is required, I wanna instantiate a original JPAQuery and then query fetchJoin-canceled JPAQuery. For example(in Kotlin):

val q = from(foo).innerJoin(bar).fetchJoin().where(...).orderBy(...)

val maxId = q.select(foo.id.max()).removeFlag(FETCH).fetchOne()

val result = q.where(foo.id.lt(maxId)).fetch()

DefaultQueryMetadata has addJoinFlag(...) but no removeJoinFlag(...). It would be great to have it!

jpa question

All 6 comments

The Query API follows a builder pattern in which join flags are only applied to the last join. There is no sensible way to use a removeFlag, because it would have to be called right after the intended flag was added for reliable results.

The proper way to do this is to find the query root from the joins list, and remove the flag from the join:

i.e.: query.getMetadata().getJoins().stream().filter(join -> join.getTarget().equals(bar)).getFlags().remove(FETCH)

We could consider an API to more fluently retrieve the query root, i.e. query.getMetadata().getJoin(bar).getFlags().remove(FETCH)

Unfortunately, I have to reject the idea of the removeFlag because it just won't work as intended.

@jwgmeligmeyling
First of all, thank you for your response 👍

I don't understand that it just won't work as intended.

I already implemented it using java reflection as follow(It works!):

                val joinFlagsField: Field = metadata.javaClass.declaredFields
                    .first { it.name == "joinFlags" }
                    .also { it.isAccessible = true }

                val joinFlags: Set<JoinFlag> = joinFlagsField.get(metadata) as Set<JoinFlag>

                if (joinFlags.contains(FETCH)) {
                    joinFlagsField.set(metadata, CollectionUtils.removeSorted(joinFlags, FETCH))
                }

I don't like that, of course. Because it is implemented using reflection as I said.

The way to find the query root from the joins list, yes it is a clear valid method, but not universal. I wanna make my ItemReader universal; As your method, ItemReader must know what is join path.

I hope you consider it one more time. 🙏

Its not universal, because one might have several joins:

from(foo)
    .innerJoin(bar).fetchJoin()
    .innerJoin(baz).fetchJoin()
    .innerJoin(buz)
   .where(...).orderBy(...)

Calling removeFetchJoin here would attempt to remove the fetch flag from buz - which is not even present - and it will leave the fetch joins for bar and baz alone.

Your implementation will be much more reliable (and not require any reflection) by using getMetadata().getJoins()#getFlags()

Mind that you can also just remove all FETCH flags: query.getMetadata().getJoins().forEach(join -> join.getFlags().remove(FETCH))

@jwgmeligmeyling
Thank you for your help! In my case, your last help is best!

Good luck!

Was this page helpful?
0 / 5 - 0 ratings