3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaでJPAを使った開発をしていると、Criteria APIでテーブルをJOINしたい場面が多くあります。
本記事では2つのJOIN方法を紹介し、それぞれの使い方や注意点、使い分けのポイントを整理します。

モデルクラスの前提(Order, Customer, Product)

Order.java
@Entity
public class Order {
    @Id
    private Long id;

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;

    @OneToOne
    @JoinColumn(name = "product_id")
    private Product product;
}
Customer.java
@Entity
public class Customer {
    @Id
    private Long id;
    private String name;
}
Product.java
@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() を使った方法①がシンプルで安全です。

モデルやエンティティにリレーションを定義したくないなどの場合は方法②を選びましょう。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?