JavaでJPAを使った開発をしていると、Criteria APIでテーブルをJOINしたい場面が多くあります。
本記事では2つのJOIN方法を紹介し、それぞれの使い方や注意点、使い分けのポイントを整理します。
モデルクラスの前提(Order, Customer, Product)
@Entity
public class Order {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
@OneToOne
@JoinColumn(name = "product_id")
private Product product;
}
@Entity
public class Customer {
@Id
private Long id;
private String name;
}
@Entity
public class Product {
@Id
private Long id;
private String name;
}
方法①:join() で結合する方法
final Root<Order> root = query.from(Order.class);
final Join<Order, Customer> customerJoin = root.join("customer", JoinType.INNER);
final Join<Order, Product> productJoin = root.join("product", JoinType.LEFT);
特徴
・@ManyToOne や @OneToOne などのリレーションがEntityに定義されている必要がある。
→リレーション単位でエンティティを分けて設計する必要があります。
・JoinType.LEFT などを指定すれば、LEFT JOINも簡単に実現可能。
・リレーションが明示された構造で可読性・保守性にも優れる。
方法②:from() を使用してWHERE句で結合する方法
final Root<Order> orderRoot = query.from(Order.class);
final Root<Customer> customerRoot = query.from(Customer.class);
final Root<Product> productRoot = query.from(Product.class);
// 結合条件をWHERE句で指定
where.add(criteriaBuilder.equal(orderRoot.get("customerId"), customerRoot.get("id")));
where.add(criteriaBuilder.equal(orderRoot.get("productId"), productRoot.get("id")));
特徴
・Entityにリレーションを記載せずに結合できるため、柔軟な設計が可能。
・JOIN条件はすべて WHERE で手書きとなるため、クロスJOINになりやすい・JOIN条件漏れのリスクが高い。
・LEFT JOINのような外部結合を行うことができず、INNER JOINしかできない。(JOIN種別を制御する仕組みがないため)
⚠注意:from()とjoin()の混合はJOIN順に注意!
次のように、INNER JOINしたいテーブルをfrom()で定義し、LEFT JOINだけjoin()で書くコードは一見問題なさそうに見えます。
final Root<Order> root = query.from(Order.class);
final Root<Customer> customerRoot = query.from(Customer.class);
final Join<Order, Product> productJoin = root.join("product", JoinType.LEFT);
// 結合条件をWHERE句で指定
where.add(criteriaBuilder.equal(orderRoot.get("customerId"), customerRoot.get("id")));
このとき、期待するSQLの形としては次のような「INNER JOIN → LEFT JOIN」の順が理想的です。
理想(INNER → LEFT のJOIN順)
SELECT *
FROM ORDER o
INNER JOIN CUSTOMER c ON o.customer_id = c.id
LEFT JOIN PRODUCT p ON o.product_id = p.id
しかし、実際にこのCriteria APIコードから生成されるSQLは、次のようになります。
現実(LEFT → INNER のJOIN順)
SELECT *
FROM ORDER o
LEFT JOIN PRODUCT p ON o.product_id = p.id,
CUSTOMER c
WHERE o.customer_id = c.id;
このSQLでは、CUSTOMER はFROM句にカンマ区切りで出現しているため、実質WHERE句での内部結合という構造になります。
その結果:
・JOINの順序が理想と異なる
・実行結果やパフォーマンスに影響が出る可能性がある
・特にLEFT JOIN側の条件やデータが複雑な場合に、期待通りの結果にならないことがある
まとめ
通常は join() を使った方法①がシンプルで安全です。
モデルやエンティティにリレーションを定義したくないなどの場合は方法②を選びましょう。