In a POJO, you can use @JoinFetch
on a field or class to filter associated entities. The difference from @Join
is that this allows fetching all lazy-related data at once.
Single Level Fetch
For example, consider a Customer entity that has a one-to-many relationship with an Order entity:
@Entity
class Customer {
@OneToMany(fetch = LAZY, cascade = ALL)
@JoinColumn(name = "order_id")
private Collection<Order> orders;
}
@Entity
class Order {
private String itemName;
}
If you want to fetch Order data when retrieving Customer, you can do:
@Data
@JoinFetch(paths = "orders")
class CustomerOrderCriteria {
@Spec
String name;
}
The executed SQL will be like:
select distinct
customer0_.* ...,
orders1_.* ...
from customer customer0_
inner outer join orders orders1_ on customer0_.id=orders1_.order_id
where customer0_.name=?
You can see that
orders1_.*
is also included in the select fields.
Join Behavior
To better align with most usage scenarios, the default behavior is as follows:
- The default join type is
INNER
. - Duplicate results are removed (
distinct
).
You can modify the default behavior by configuring @JoinFetch#joinType
or @JoinFetch#distinct
, for example:
@JoinFetch(joinType = JoinType.RIGHT, distinct = false)
With Clause
@JoinFetch
also allows filtering data. For example, if you want to filter both customer names and order names, define a POJO as follows:
@Data
@JoinFetch(path = "orders", alias = "o")
public class CustomerOrderCriteria {
@Spec
String name;
@Spec(path = "o.itemName", value = In.class)
Collection<String> items;
}
The executed SQL will be like:
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
can also be used at the field level, for example:
@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
You can use @JoinFetches
to define multi-level fetches. For example, if the Order entity has a many-to-many relationship with a Tag 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;
}
If you want to query customers who purchased items belonging to specific categories, define a POJO as follows:
@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;
}
The executed SQL will be like:
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
can also be used at the class level, making the aliases
available across fields in the same object. For example:
@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
Annotations are processed in order, so @JoinFetches
must be defined in the correct sequence.
For example, in the previous scenario, the following order is incorrect:
@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
The usage rules for @JoinFetch#alias
are as follows:
- It is shared within the same POJO
- It cannot be declared multiple times within the same POJO
- If not provided, the default alias is
@JoinFetch#path
- If it contains
.
, it will be replaced with_
For example:
@JoinFetches({
@JoinFetch(path = "orders"), // default alias is "orders"
@JoinFetch(path = "orders.tags") // default alias is "orders_tags"
})
@Spec(path = "orders_tags.name", value = In.class)