quarkus icon indicating copy to clipboard operation
quarkus copied to clipboard

Cannot project Panache entity with null @OneToOne reference

Open Wouter684 opened this issue 1 year ago • 7 comments

Describe the bug

When trying to project an entity which has a one-to-one or one-to-many relation, entities that do not have a coupled entity are not returned in query results. They are returned without projection. We noticed that Panache emits a query containing a regular join, that might be the issue.

Expected behavior

We expect that the projected result is still returned, with a null value for the coupled entity.

Actual behavior

The parent entity is not returned in query results.

How to Reproduce?

https://github.com/Wouter684/cannot-project-with-null

Run the unit test in the project. (mvn verify)

Output of uname -a or ver

No response

Output of java -version

openjdk version "21.0.3" 2024-04-16 LTS
OpenJDK Runtime Environment Temurin-21.0.3+9 (build 21.0.3+9-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.3+9 (build 21.0.3+9-LTS, mixed mode)  

Quarkus version or git rev

3.13.3

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.9.8 (36645f6c9b5079805ea5009217e36f2cffd34256)
Maven home: /Users/<redacted>/.sdkman/candidates/maven/current
Java version: 21.0.3, vendor: Eclipse Adoptium, runtime: /Users/<redacted>/.sdkman/candidates/java/21.0.3-tem
Default locale: nl_NL, platform encoding: UTF-8
OS name: "mac os x", version: "14.6.1", arch: "aarch64", family: "mac"

Additional information

No response

Wouter684 avatar Aug 21 '24 13:08 Wouter684

/cc @FroMage (panache), @loicmathieu (panache)

quarkus-bot[bot] avatar Aug 21 '24 13:08 quarkus-bot[bot]

Related question on Stack Overflow (no answer, unfortunately).

Caster avatar Aug 22 '24 08:08 Caster

Strange thing is the same query .count() returns the correct number of records, but the .list() returns empty.

kosti31 avatar Dec 20 '24 13:12 kosti31

Any news for this projection issue? Thank you.

Mohamed-Sghaier avatar Dec 10 '25 13:12 Mohamed-Sghaier

The HQL sent to Hibernate is:

SELECT new org.example.MyProjectedEntity (id,field,bar,myRelatedEntity) FROM `org.example.MyEntity` ORDER BY `id`

This is turned into this SQL:

    select
        me1_0.id,
        me1_0.field,
        me1_0.bar,
        mre1_0.id,
        mre1_0.fieldName 
    from
        MyEntity me1_0 
    join
        MyRelatedEntity mre1_0 
            on mre1_0.id=me1_0.related_entity_id 
    order by
        me1_0.id

It works for .project(MyProjectedEntity.class).count() because ORM produces this count query:

    select
        count(*) 
    from
        MyEntity me1_0

(no join, so it works)

Without projection, we get this HQL:

FROM `org.example.MyEntity` ORDER BY `id`

Turned into this SQL:

    select
        me1_0.id,
        me1_0.bar,
        me1_0.field,
        me1_0.foo,
        me1_0.related_entity_id 
    from
        MyEntity me1_0 
    order by
        me1_0.id

Notice the lack of join, which makes it work.

This sounds like an upstream ORM issue, no @lucamolteni @yrodiere ?

FroMage avatar Dec 10 '25 14:12 FroMage

Yes @FroMage, the difference is the @OneToOne association is joined in the projection (i.e. dynamic instantiation) query, since we need the result immediately. I'm guessing in the other query, the join is not produced but you get a subsequent select for the related entity (if the FK value is non-null).

Hibernate will always produce an inner join for selected associations, including this case where the selection is used as argument for the dynamic instantiation. To produce a left-join, the correct way to generate the query would be:

SELECT new org.example.MyProjectedEntity(id, field, bar, related) FROM `org.example.MyEntity` m left join m.myRelatedEntity related ORDER BY m.id

mbellade avatar Dec 10 '25 16:12 mbellade

Related, though not exactly the same problem since the "navigation operator" is implicit in your case:

https://github.com/quarkusio/quarkus/issues/36497 https://github.com/jakartaee/persistence/issues/697 https://hibernate.atlassian.net/issues/HHH-19401

I guess if we introduce a safe navigation operator, you'll be able to use this?.related in the projection?

Not saying it's great, but it's something, at least :)

yrodiere avatar Dec 10 '25 17:12 yrodiere

Well, not great, no, because it means I'd have to either prefix every projection part with this?. (well, I guess this is easy if it works for every member type), or inspect the members to see whether I need to prefix it.

I'll take a look at how ORM projections deal with this, and Jakarta Data.

FroMage avatar Dec 11 '25 13:12 FroMage

ORM projections with:

session.createSelectionQuery("select id, field, bar, myRelatedEntity from MyEntity", MyProjectedEntity.class).list();

Behave the same and generate:

    select
        me1_0.id,
        me1_0.field,
        me1_0.bar,
        mre1_0.id,
        mre1_0.fieldName 
    from
        MyEntity me1_0 
    join
        MyRelatedEntity mre1_0 
            on mre1_0.id=me1_0.related_entity_id

So that's coherent.

I'm not sure how to test JD projections, I think I would need a snapshot of ORM since projections are in Jakarta Data 1.1 IIUC.

Well, I'm not sure how to resolve this issue then. Call it a "known trap that is not a bug" and document it?

I guess we're not changing the JPA spec for this. On the other hand it does seem counterintuitive.

Any opinion, @gavinking ?

FroMage avatar Dec 11 '25 13:12 FroMage