EntityGraphのバグに遭遇した話
何が起こったか
Spring Bootを2.7から3.1にアップグレードしたところ、EntityGraph
を使用した場合の動作が変化し、期待通りの結果が取得できなくなりました。
具体的には、不要な2重のJOIN
がクエリ内で生成される問題が生じました。
Spring Bootのアップグレードに伴い、Hibernateのアップグレードも必要だったので、あわせてHibernateを5.6.1から6.4.0に変更しました。この変更が問題の要因となったようです。
EntityGraphとは
EntityGraph
は、JPAの機能で、エンティティを取得するときに関連エンティティの取得戦略を動的に制御するための手段です。これにより、N+1問題を回避したり、必要なデータだけを効率よく取得することができます。
本記事のケースでは、Childテーブルのsex
を条件にフィルタリングし、データ取得することを目的として使用しています。
データ構造とクエリ
以下のようなデータ構造のエンティティがあります。
@Entity
public class Parent {
@Id
private Long id;
@OneToMany(mappedBy = "parent")
private List<Child> children;
}
@Entity
public class Child {
@Id
private Long id;
private int sex ;
@ManyToOne
private Parent parent;
}
この Parent
エンティティに紐づく Child
エンティティを特定の条件(sex
)でフィルタリングするために、EntityGraph
を使用してクエリを作成していました。
@EntityGraph(attributePaths = {"children"})
List<Parent> findByIdInAndChildrenSex(List<Long> ids, int sex);
アップグレード前は、Parent
エンティティに紐づくChild
エンティティがsex
でフィルタリングされ必要なデータのみ取得できていました。
SELECT ...
FROM parent p
LEFT JOIN child c1 ON p.id = c1.parent_id
WHERE p.id in (?, ?)
AND c.sex = ?
ところが、アップグレード後にはchild
を2重に結合したクエリが生成されるようになりました。
SELECT ...
FROM parent p
LEFT JOIN child c1 ON p.id = c1.parent_id
LEFT JOIN child c2 ON p.id = c2.parent_id
WHERE p.id in (?, ?)
AND c.sex = ?
その結果、予期しないデータを取得するようになったことで集計が倍増するなどの問題が発生しました。
原因
以下の報告によるとこの問題は確認されており、Hibernate6.4.3で修正されているようです。
この問題の原因は、Hibernateが5系から6系へのバージョンアップで内部処理に変更が加えられたことのようです。
これにより、EntityGraph
を使用したクエリ生成で以前のバージョンでは起きなかった問題が新たに発生しました。
対応したこと
この問題への対応として、@Query
を使用して独自のクエリを定義する方法を採用しました。
これにより、不要な2重結合を回避し期待通りの結果を取得できるようになりました。
@Query("SELECT p FROM Parent p JOIN p.children c WHERE p.id IN :ids AND c.sex = :sex")
List<Parent> findByIdInAndChildrenSex(List<Long> ids, int sex);
終わりに
Spring BootやHibernateをアップグレードする際には、期待する動作が変更されることがあるため注意が必要です。同じような問題に遭遇された方の参考になれば幸いです。
私たち BABY JOB は、子育てを取り巻く社会のあり方を変え、「すべての人が子育てを楽しいと思える社会」の実現を目指すスタートアップ企業です。圧倒的なぬくもりと当事者意識をもって、こどもと向き合う時間、そして心のゆとりが生まれるサービスを創出します。baby-job.co.jp/
Discussion