JOINS
join出现在两个或多个实体联合查询产生一个JPQL查询结果。JPQL中join与SQL中的SQL相似。最后,要说明的是,所有的JPQL会转换成SQL查询。出现以下情况时,就可以用上join。
-
访问集合关联字段的路径表达式出现在
SELECT
语句
-
join保留字出现在
WHERE
语句中
-
定义两个或多个界定变量
如果你在一个查询中使用了一个以上的实体,你会获取所有实体的实例。这一结果称为Cartesian product(卡笛尔产品)。假使你的系统中有8个角色和4个用户,下面的查询会得到32个对象。
SELECT r, u FROM Role r, User u
你可能想用某种join来减少查询结果的数量。如果你想通过字段而不是通过主键来关联实体,你可以使用theta-join。例如:
SELECT t FROM Topic t, Forum f WHERE t.postCount = f.forumPostCount
这条查询语句返回的是与论坛回复(Forum)数相同的主题(Topic)。theta-join允许你关联那些没有显式关联关系或者关联到没有关系但是等同的信息的实体。
JPQL中inner join也是用于关系联合。其语法如下:
[INNER] JOIN join_association_path_expression [AS] identification_variable
INNER
和
AS
都是可选的。虽然它们对查询没有什么影响,你还是可以使用它们来更加清晰的表达你的意图。
join_assocation_path_expression
的意思是你可以访问一个关联实体,不管是单值关联还是一个集合。下图演示了两个inner join查询:
图 3.
因为你不能在
SELECT
语句中使用一个集合关联字段,JPQL为你提供了
INNER JOIN
操作符。如果你想访问
Forum-Topic-Post
的关联关系(如图2所示),获得所有Post的标题,你可以使用这样一条查询语句:
SELECT p.title FROM Forum f JOIN f.topics AS t JOIN t.posts AS p
这条查询会返回0个或多个
String
对象,封装的是
Forum-Topic-Post
联合查询的回复的标题。
一个outer join会返回一个实体的所有实例和其它与join criteria匹配的其它实体的实例。一个left join的语法如下:
LEFT [OUTER] JOIN join_association_path_expression [AS] identification_variable
[OUTER]是可选的,因为在JPQL中
LEFT JOIN
和
LEFT OUTER JOIN
以认为是等同的。使用上图中所示的
Forum/Post
实体关系,下面的left join操作可以读取所有的
Forum
和任何与
Forum
关联的
Topic
。如果找不到
Topic
,
Object
数组的第2项的值就是null。
SELECT f, t FROM Forum f LEFT JOIN f.topics t
使用范例数据库,上面的查询会返回以下结果:
[ Object: [Forum] Object: [Topic] ]
[ Object: [Forum] Object: [null] ]
[ Object: [Forum] Object: [null] ]
[ Object: [Forum] Object: [null] ]
[ Object: [Forum] Object: [null] ]
这个查询返回了所有的
Forum
实例和一个唯一的
Topic
,因为仅有一个
Forum
有一个
Topic
(看起来留言板需要更多的用户参与)。
由于
LEFT JOIN
操作符是预读取的一种有效方式,JPA还提供了
FETCH JOIN
操作。
FETCH JOIN
操作符在下一节介绍。
fetch join允许你创建查询来预读取另外一种lazy关联关系。如果你了解到在所有的实体读取之后并可能处于脱管状态时就有必要使用
LAZY
关联关系,你就可以使用
FETCH JOIN
来读取这种关系。
FETCH JOIN
如下:
[LEFT [OUTER] INNER] JOIN FETCH join_association_path_expression
和前面定义不同的是,
FETCH JOIN
没有范围变量,因为你不能在查询语句中使用隐式引用的实体。下面的查询会读取任何有主题(
Topic
)的论坛(
Forum
)实体,仅仅那有主题(
Topic
)的论坛(
Forum
)实体才会被读取。下面代码演示了如何定义论坛(
Forum
)实体。注意与主题(
Topic
)的关联关系是lazy。
@Entity
public class Forum extends ModelBase implements Serializable {
@OneToMany( fetch=FetchType.LAZY,
cascade={CascadeType.PERSIST,
CascadeType.MERGE},
mappedBy="forum")
@OrderBy("type asc, dateUpdated desc")
Set<Topic> topics = new HashSet<Topic>();
// ...
}
这里有一条查询语句:
SELECT DISTINCT f FROM Forum f JOIN FETCH f.topics
因为数据库中有5个Forum实例,但仅有一个有Topic。上面的查询语句会返回一个Forum实例,Topic的关联关系也立即读取。如果你不使用
DISTINCT
,持久化实现方案(persistence provider)会为系统中每个返回一个Forum实例。使用了
DISTINCT
,重复的实例会被删除。
要读取所有的论坛(
Forum
)实例,并立即读取它们的主题(
Topic
),如下在的查询语句所示,
SELECT DISTINCT f FROM Forum f LEFT JOIN FETCH f.topics
这条返回所有唯一的论坛(
Forum
)实例,并且所有的主题(
topics
)已经预先读取了。使用
JOIN FETCH
的一个缺点是需要知道对象模型。一旦你知道关联关系的类型,就可以用
JOIN FETCH
优化你的查询。
查询中的
WHERE
语句是由条件表达式组成,由它决定返回的实体。只要你将使用
GROUP BY
的字段放到
SELECT
语句中,你可以用
GROUP BY
来统计查询结果。你还可以用
HAVING
过滤查询结果。JPA并没有要求持久化实现在不使用
GROUP BY
支持
HAVING
,可以保持可移植性,你最好不要在
GROUP BY
外使用
HAVING
。
图 4.
Post/User
对象关系图
现在假设你想知道创建的
agoraBB
系统中每个用户的发帖数量。从上图中可以看,没有从
User
到
Post
的关联关系。因为
User
和
Post
继承
ModelBase MappedSuperclass
的,我们知道每个
Post
对象有一个
createdByUser
和
updatedByUser
字段。
使用inner join的话,你可以写查询语句:
SELECT count(p) From Post p JOIN p.createdByUser u
问题是,这条查询会返回所有的由
createdByUser
字段指定用户创建的所有帖子的数量。如果你想知道每个用户创建了多少帖子,你必须使用
GROUP BY
操作符:
SELECT u, count(p) From Post p JOIN p.createdByUser u GROUP BY u