Hibernate changes outer to inner join when @EntityGraph and @Id

I’m using @EntityGraph to load some attributes eagerly. But sth has broken after update from 5.x to 6.x.

Let say we have a human who can have multiple houses:

public class Human {
    @Id
    @Column(name = "ID", nullable = false, updatable = false, precision = 20)
    @GeneratedValue
    private BigInteger id;

    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    @OneToMany(mappedBy = "human", cascade = CascadeType.ALL)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Collection<House> houses;
}

When search for him, by id, EntityGraph can load something eagerly:

@EntityGraph(attributePaths = { "houses.address" })
@Query ("SELECT h FROM Human h WHERE h.id IN ?1")
Human findByIdEagerHouseAddresses(Integer id);

this will create sql with left (outer by default) joins:

select h1_0.id,h2_0.human_fk,h2_0.address_fk,a1_0.id 
from human h1_0 
left join house h2_0 on h1_0.id=h2_0.human_fk
left join address a1_0 on a1_0.id=h2_0.address_fk where h1_0.id in (?)

This is ok, I will get my human, even if it has no house.

But if address field in house will have annotation @Id, outer join will change to “inner”:

@Entity
public class House {
    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "human_fk", nullable = false, updatable = false)
    private Human human;

    @Id  // THIS ANNOTATION WAS ADDED
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "address_fk", nullable = false, updatable = false)
    private Address address;
}
select h1_0.id,h2_0.human_fk,h2_0.address_fk,a1_0.id 
from human h1_0 
left join house h2_0 on h1_0.id=h2_0.human_fk 
join address a1_0 on a1_0.id=h2_0.address_fk where h1_0.id in (?)

If a human does not have a house, query returns nothing, because of join (which is inner), which seems like a bug.

Is it really a bug? or a feature? I have checked this on latest 3.1.1 spring boot.

This can only be a problem if you don’t have a foreign key constraint on the database. In that case, you will have to tell Hibernate that by annotating @NotFound(IGNORE), but you really shouldn’t model your database this way.
Which Hibernate version are you using?

Thank you for answer!

At first glance, I thought so too, but the inner join propagates from the address to the whole query. So, when we have one record in the ‘human’ table, with no associated house (which implies that the ‘address’ table is empty),

SELECT * FROM human LEFT OUTER JOIN house → returns 1 row
SELECT * FROM human LEFT OUTER JOIN house INNER JOIN address → returns 0 rows

There can be a constraint between the ‘address’ and ‘house’ tables, but it’s completely okay for a human to be “homeless”.

I prepared test case in github (based on hibernate template) without spring.

Regarding versions:

  • Hibernate 6.0.0 - 6.1.5 → combination of @EntityGraph and @Id cause the final query to omit completely left outer join for entityGraphs attributes
  • Hibernate 6.1.6 - 6.2.6 (latest 6.2.x) → entityGraph attributes are there again, but query has inner join instead of outer join

Thanks for your test case template. Would be so kind and create a JIRA issue for this, linking to this discussion and the test case project you created?

I’ve already created a ticket, but no one seems to be interested in it. :frowning:
Perhaps I wasn’t clear enough in my description, or maybe the description is too extensive?
https://hibernate.atlassian.net/jira/software/c/projects/HHH/issues/HHH-16885

Is there any way I can make this ticket more appealing?

From our perspective, it’s a blocker, as we are heavily dependent on entitygraphs. Due to bugs in Hibernate version 6 and above, we’re unable to upgrade to Spring Boot 3.

If you can’t wait until the Hibernate Team finds the time to fix this bug, you will have to fix it yourself. It’s an open source project after all and everyone has to pick their battles.