Upgrading to Hibernate 6, EntityGraph annotation has not the same behaviour as before

Hello everyone,

Recently I upgraded my project from Sprint Boot 2.5.14 to 3.1.5
By the way Hibernate to 5.4.33 to 6.2.13

This request works fine before upgrading, it seems that @entitygraph is not working as before

Repository

@EntityGraph(attributePaths = { "releaseFeatures" })
  @Query(
    "SELECT release FROM Release release LEFT JOIN release.releaseFeatures rf " +
    "WHERE rf.isDeleted IS FALSE AND ((release.id = 1 and release.isPublished = true) OR (release.id = 2))"
  )
  List<Release> findActive();

Entities

@Entity
@Table(name = "releases")
public class Release implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Enumerated(EnumType.STRING)
  private ReleaseVersionEnum version;

  @JsonDeserialize(converter = DateRFC1123ToZonedDateTimeConverter.class)
  private ZonedDateTime dateToPublish;

  @JsonDeserialize(converter = DateRFC1123ToZonedDateTimeConverter.class)
  private ZonedDateTime dateOfDeployment;

    private boolean isPublished;

  @OneToMany(mappedBy = "release", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<ReleaseFeature> releaseFeatures = new HashSet<>();```

...
}
@Entity
@Table(name = "release_features")
public class ReleaseFeature implements Serializable {

  @JsonIgnore
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @NaturalId
  @Column(name = "uuid", unique = true)
  private String uuid;

  @JsonIgnore
  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "applications", "hibernateLazyInitializer" })
  @JoinColumn(name = "release_id", nullable = false)
  private Release release;
....
}

Table Release

id version date_to_publish date_of_deployment is_published
1 CURRENT 2024-02-23 00:00:00.000 2024-02-23 00:00:00.000 true
2 NEXT null null false

Table Release_features

id uuid release_id title_translation_code description_translation_code is_deleted deleted_date
44 uuid-1 1 RELEASE_CONTENT_TITLE_01_FR RELEASE_CONTENT_DESC_01_FR true 2022-04-12 09:46:36.260
45 uuid-2 1 RELEASE_CONTENT_TITLE_02_FR RELEASE_CONTENT_DESC_02_FR true 2022-04-12 09:46:36.260
46 uuid-2 1 RELEASE_CONTENT_TITLE_03_FR RELEASE_CONTENT_DESC_03_FR true 2022-04-12 09:46:36.260
47 uuid-2 1 RELEASE_CONTENT_TITLE_04_FR RELEASE_CONTENT_DESC_04_FR false 2022-04-12 09:46:36.260

Hibernate debug
version 5.4.33

select
    release0_.id as id1_37_0_,
    releasefea1_.id as id1_36_1_,
    release0_.date_of_deployment as date_of_2_37_0_,
    release0_.date_to_publish as date_to_3_37_0_,
    release0_.is_published as is_publi4_37_0_,
    release0_.version as version5_37_0_,
    releasefea1_.deleted_date as deleted_2_36_1_,
    releasefea1_.description_translation_code as descript3_36_1_,
    releasefea1_.is_deleted as is_delet4_36_1_,
    releasefea1_.release_id as release_7_36_1_,
    releasefea1_.title_translation_code as title_tr5_36_1_,
    releasefea1_.uuid as uuid6_36_1_,
    releasefea1_.release_id as release_7_36_0__,
    releasefea1_.id as id1_36_0__
from
    releases release0_
        left outer join
    release_features releasefea1_
    on release0_.id=releasefea1_.release_id
where
    releasefea1_.is_deleted=0
  and (
    release0_.id=1
        and release0_.is_published=1
        or release0_.id=2
    )

Version 6.2.13

select
    r1_0.id,
    r1_0.date_of_deployment,
    r1_0.date_to_publish,
    r1_0.is_published,
    r1_0.version
from
    releases r1_0
        left join
    release_features r2_0
    on r1_0.id=r2_0.release_id
where
    r2_0.is_deleted=0
  and (
    (
        r1_0.id=1
            and r1_0.is_published=1
        )
        or (
        r1_0.id=2
        )
    )

select
    r1_0.release_id,
    r1_0.id,
    r1_0.deleted_date,
    r1_0.description_translation_code,
    r1_0.is_deleted,
    r1_0.title_translation_code,
    r1_0.uuid
from
    release_features r1_0
where
    r1_0.release_id=1;

With 6.2.13, I had this Trace:
JdbcValuesSourceProcessingStateStandardImpl: Skipping create subselects because there are fewer than 2 results, so query by key is more efficient.

Instead of returning the only line from release_features ( is_deleted = false ), it returns all lines

Is it possible to have the same behaviour as before ? May be I miss some configurations ?

The result you are receiving now is correct and ORM 5 had a bug. If you want to filter collections, use Hibernate filters or the @Where annotation. Alternatively, you can also make use of the new soft delete feature of ORM 6.4.

Thanks for your feedback
Finally, I removed EntityGraph annotation and used LEFT JOIN FETCH

@Query(
    "SELECT release FROM Release release LEFT JOIN FETCH release.releaseFeatures rf " +
    "WHERE rf.isDeleted = FALSE AND ((release.id = 1 and release.isPublished = true) OR (release.id = 2))"
  )
  List<Release> findActive();

Doing that comes with the risk that Hibernate ORM might remove data that you filtered out if the collection is changed. I strongly recommend you to use one of the supported variants that I suggested.

Thank you for your help, finally I applied your recommendations

@Entity
@Table(name = "releases")
public class Release implements Serializable {

....
  @OneToMany(mappedBy = "release", cascade = CascadeType.ALL, orphanRemoval = true)
  @Fetch(FetchMode.JOIN)
  @Where(clause = "is_deleted = 0")
  private Set<ReleaseFeature> releaseFeatures = new HashSet<>();
....
}
@Repository
public interface ReleaseRepository extends JpaRepository<Release, Long> {
  @Query(
    "SELECT release FROM Release release " +
    "WHERE ((release.id = 1 and release.isPublished = true) OR (release.id = 2))"
  )
  List<Release> findActive();
}