Join Fetch
在 POJO 中, 你可以在 Field 或 Class 上使用 @JoinFetch
來過濾關聯的 Entity, 跟 @Join
的差別是, 這可以一次撈出所有 Lazy 的關聯資料
Single Level Fetch
例如, 有個客戶 Entity, 會一對多的關聯訂單 Entity:
@Entity
class Customer {
@OneToMany(fetch = LAZY, cascade = ALL)
@JoinColumn(name = "order_id")
private Collection<Order> orders;
}
@Entity
class Order {
private String itemName;
}
如果你想在取得 Customer 時就順便取得 Order, 則:
@Data
@JoinFetch(paths = "orders")
class CustomerOrderCriteria {
@Spec
String name;
}
執行的 SQL 會類似:
select distinct
customer0_.* ...,
orders1_.* ...
from customer customer0_
inner outer join orders orders1_ on customer0_.id=orders1_.order_id
where customer0_.name=?
你可以看到
orders1_.*
也被放入了 select 項目內
Join Behavior
為了比較符合大部分的使用情境, 以下是預設的行為:
- Join type 預設為
INNER
- 將結果排除重複 (
distinct
)
透過設定 @FetchJoin#joinType
或 @FetchJoin#distinct
可以改變預設行為, 如:
@FetchJoin(joinType = JoinType.RIGHT, distinct = false)
With Clause
@JoinFetch
也可以讓你過濾資料, 例如, 我想要過濾客戶名稱及訂單名稱, 則可以定義 POJO 如下:
@Data
@JoinFetch(path = "orders", alias = "o")
public class CustomerOrderCriteria {
@Spec
String name;
@Spec(path = "o.itemName", value = In.class)
Collection<String> items;
}
執行的 SQL 會類似:
select distinct
customer0_.* ...,
orders1_.* ...
from customer customer0_
inner outer join orders orders1_ on customer0_.id=orders1_.order_id
where customer0_.name=? and orders1_.item_name in (?)
@JoinFetch
也可以用在 Field 層級, 例如:
@Data
public class CustomerOrderCriteria {
@Spec
String name;
@JoinFetch(path = "orders", alias = "o")
@Spec(path = "o.itemName", value = In.class)
Collection<String> items;
}
Multi Level Fetches
你可以使用 @JoinFetches
來定義多層級的 Fetch, 例如, 在剛剛的訂單 Entity 中, 還會多對多的關聯到類別 Entity:
@Entity
class Customer {
@OneToMany(cascade = ALL, fetch = LAZY)
@JoinColumn(name = "order_id")
private Set<Order> orders;
}
@Entity
class Order {
@ManyToMany(cascade = ALL, fetch = LAZY)
private Set<Tag> tags;
}
@Entity
class Tag {
private String name;
}
如果你想要查詢買了指定所屬類別的東西的客戶, 則可以定義 POJO 如下:
@Data
class CustomerOrderTagCriteria {
@JoinFetches({
@JoinFetch(path = "orders", alias = "o"),
@JoinFetch(path = "o.tags", alias = "t")
})
@Spec(path = "t.name", value = In.class)
Collection<String> tags;
}
執行的 SQL 會類似:
select distinct
customer0_.* ...,
orders1_.* ...,
tag3_.* ...
from customer customer0_
inner join orders orders1_ on customer0_.id=orders1_.order_id
inner join orders_tags tags2_ on orders1_.id=tags2_.order_id
inner join tag tag3_ on tags2_.tags_id=tag3_.id
where tag3_.name in (?)
@JoinFetches
也是可以用在 class 層級, 在同一個物件內的欄位就都可以使用 alias
來對 Join 的對象增加條件, 例如:
@Data
@JoinFetches({
@JoinFetch(path = "orders", alias = "o"),
@JoinFetch(path = "o.tags", alias = "t")
})
public class CustomerOrderCriteria {
@Spec(path = "o.itemName", value = In.class)
Collection<String> items;
@Spec(path = "t.name", value = In.class)
Collection<String> tags;
}
Fetches Order
Annotation 的處理是有順序性的, 因此必須依照 Join 的順序去定義 @JoinFetches
例如依照上面的情境, 下列的定義順序是錯誤的:
@Data
class CustomerOrderTagCriteria {
@JoinFetches({
@JoinFetch(path = "o.tags", alias = "t"), // "o" alias will be not exist during processing this @Join
@JoinFetch(path = "orders", alias = "o")
})
@Spec(path = "t.name", value = In.class)
Collection<String> tagNames;
}
Alias
@JoinFetch#alias
的使用規則如下:
- 在同個 POJO 中是共用的
- 在同的 POJO 中不可重複宣告
- 若沒提供, 預設使用
@JoinFetch#path
- 若包含了
.
會以_
取代之
例如:
@JoinFetches({
@JoinFetch(path = "orders"), // alias 預設為 orders
@JoinFetch(path = "orders.tags") // alias 預設為 orders_tags
})
@Spec(path = "orders_tags.name", value = In.class)