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查询:
因为你不能在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
。
现在假设你想知道创建的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