44.9. Java持久查询语言

摘要

Java持久查询语言(Java Persistence query language)是EJB3.0规范对EJB-QL(Enterprise JavaBeans query language)的扩展。本节重点介绍在EJB3.0规范中新增或改进的特性。EJB2.1规范中定义的EJB-QL说明可参考第 44.7 节 “EJB QL”

44.9.1. 新特性

Java持久查询是EJB-QL的扩展,在原EJB-QL的基础上,增加或改进了一系列新特性,包括批量更新与删除、JOIN操作、GROUP BY从句、HAVING从句、投影、子查询、支持静态查询与动态查询、支持顺序参数与命名参数

44.9.1.1. 批量更新与删除

Java持久查询语言支持一次性完成多条记录的更新与删除动作。批量更新与删除每次只能对一个实体类的多个实例进行操作(包括任何子类实例)。在FROM与UPDATE子句中只能指定一个抽象模式类型(关于抽象模式类型的说明可参考第 44.7.2.1 节 “抽象持久类型与查询范围”)。批量更新与删除操作的BNF语法结构如下:

update_statement ::= update_clause [where_clause]
update_clause ::= UPDATE abstract_schema_name [[AS] identification_variable]
                      SET update_item {, update_item}*
                      update_item ::= [identification_variable.]{state_field | 
                                       single_valued_association_field} =
                                  new_value
new_value ::=
       simple_arithmetic_expression |
       string_primary |
       datetime_primary |
       boolean_primary |
       enum_primary
       simple_entity_expression |
       NULL
delete_statement ::= delete_clause [where_clause]
delete_clause ::= DELETE FROM abstract_schema_name [[AS] identification_variable]

使用批量删除与更新时须注意以下几点:

  • 批量删除操作只对指定的类及其子类执行,不会级联删除关联的实体;

  • 批量更新中指定的新值的类型必须与数据库中的目标字段类型兼容;

  • 批量更新直接对数据库进行操作,绕过了乐观锁检查。应用需要自行更新与校验version字段的值;

  • 持久化上下文不会同操作结果进行同步。因此使用批量更新与删除可能会令持久化上下文中的实体与数据库记录不一致。通常来说,批量更新与删除应该在单独的事务中使用;或者在事务刚开始,可能会受到影响的实体还未被访问时使用。

以下是一些批量删除与更新的Java持久查询语言例子:

DELETE
FROM Customer c
WHERE c.status = 'inactive'

DELETE
FROM Customer c
WHERE c.status = 'inactive'
  AND c.orders IS EMPTY

UPDATE customer c
SET c.status = 'outstanding'
WHERE c.balance < 10000
  AND 1000 > (SELECT COUNT(o)
                FROM customer cust JOIN cust.order o)

44.9.1.2. 连接操作

连接(JOIN)是关系数据库中常见的操作。Java持久查询语言支持内连接与左连接。

可以通过在WHERE子句中指定连接条件的方式来隐式使用内连接:

select c from Customer c, Employee e where c.hatsize = e.shoesize

通常,当连接条件没有涉及已定义的实体关系时,使用这种方式进行内连接。当通过已定义的实体关系进行连接时,使用显式的连接语法。

显式连接的BNF语法结构如下:

join ::= join_spec join_association_path_expression [AS] identification_variable
fetch_join ::= join_spec FETCH join_association_path_expression
join_association_path_expression ::= join_collection_valued_path_expression |
join_single_valued_association_path_expression
join_spec::= [ LEFT [OUTER] | INNER ] JOIN
44.9.1.2.1. 内连接(关系连接)

内连接的BNF语法结构是:

[ INNER ] JOIN join_association_path_expression [AS] identification_variable

Java持久查询语言的默认连接方式是内连接。例如,以下查询语句通过实体关系连接了顾客与订单两个实体。一般来说, 这个连接相当于在数据库中通过外键进行连接。

SELECT c FROM Customer c JOIN c.orders o WHERE c.status = 1

也可以显式使用INNER关键字:

SELECT c FROM Customer c INNER JOIN c.orders o WHERE c.status = 1

以上查询语句等价于先前的EJB-QL中的IN结构,查询出所有status为1,且至少关联了一个订单的顾客:

SELECT OBJECT(c) FROM Customer c, IN(c.orders) o WHERE c.status = 1
44.9.1.2.2. 左连接(LEFT JOIN)

左连接,或者叫左外连接(LEFT OUTER JOIN),在进行连接时保证JOIN关键字左边的表(主表)的实例在结果表中至少出现 一次,允许被匹配的值(右表的值)为空。

左连接的语法为:

LEFT [OUTER] JOIN join_association_path_expression [AS] identification_variable

例如:

SELECT c FROM Customer c LEFT JOIN c.orders o WHERE c.status = 1

也可以显式使用OUTER关键字:

SELECT c FROM Customer c LEFT OUTER JOIN c.orders o WHERE c.status = 1
44.9.1.2.3. 获取连接(FETCH JOIN)

获取连接允许在执行查询时同时获取关联的实体。获取连接必须基于实体与其所关联的实体进行。

获取连接的语法为:

fetch_join ::= [ LEFT [OUTER] | INNER ] JOIN FETCH join_association_path_expression

FETCH JOIN子句右边的实体引用必须是结果实体所持有的关系引用。在获取连接中,不能为FETCH JOIN子句右边的实体引用指定标识变量,因此这个隐式获取的实体引用不允许在查询语句的其它位置出现。

以下的查询返回一个部门(department)实体的集合,同时获取这些部门所关联的雇员(employee),但雇员实体并不显式地在结果表中出现。雇员实体中所有声明为“立即获取”的持久值域或属性都将被初始化。雇员的关系属性的初始化动 作则由雇员实体类中的元数据决定。

SELECT d
FROM Department d LEFT JOIN FETCH d.employees
WHERE d.deptno = 1

获取连接在语义上同内连接与左连接一样,不同之处在于JOIN子句右边的被关联实体不会在结果中出现,也不能在查询语句的其它位置引用。

获取连接的优先级比关系中指定的FetchType优先级更高,因此一个常见使用场景是当实体的元数据指定为延迟获取时,使用左连接获取(LEFT JOIN FETCH)来对查询结果所关联的实体进行预先获取。

44.9.1.3. GROUP BY和HAVING子句

GROUP子句根据一个属性集合对结果进行分类聚合运算。HAVING子句可以指定基于分类的条件,进一步对结果进行过滤。

GROUP BY与HAVING子句的BNF语法描述如下:

groupby_clause ::= GROUP BY groupby_item {, groupby_item}*
groupby_item ::= single_valued_path_expression | identification_variable
having_clause ::= HAVING conditional_expression

使用GROUP BY和HAVING子句时,应注意以下几点:

  • 如果查询中同时含有WHERE子句与GROUP BY子句,执行效果相当于先执行WHERE子句,然后根据GROUP BY子句对过滤结果进行分组,最后根据HAVING子句对分组进行进一步过滤;

  • 除了聚合函数(COUNT,SUM,AVG等)外,在SELECT子句中出现的任何参数必须在GROUP BY子句中出现;

  • 在进行分类时,两个空值被认为是相等的;

  • 允许使用实体作为分类依据,但此实体不能包含已序列化的持久值域或实际取值为LOB类型的持久值域;

  • HAVING子句指定的条件必须基于GROUP BY子句的参数和它们的聚合函数。

例如:

SELECT c.status, avg(c.filledOrderCount), count(c)
FROM Customer c
GROUP BY c.status
HAVING c.status IN (1, 2)

SELECT c.country, COUNT(c)
FROM Customer c
GROUP BY c.country
HAVING COUNT(c.country) > 3

44.9.1.4. 投影

投影允许查询中的SELECT子句指定返回实体的特定属性。在不要求返回整个实体的场合中,使用投影可以提高执行效率。例如:

SELECT 
  b.name, p.name
FROM
  Book b, Publisher p
WHERE
  e.publisher = p

使用投影时,应注意以下两点:

  • SELECT子句的每项参数必须返回单值。因此以下查询是不合法的:

    SELECT o.lineItems FROM Order AS o

  • 查询返回的结果集为Vector类型。若投影中只涉及一个属性,则结果集的元素类型为该属性类型或其包装类(当属性为Java基本类时);若投影中涉及多个属性,则结果集的元素为对象数组Object[],该对象数组的元素依次为SELECT列表中的属性的类型或其包装类。如果不想使用对象数组作为结果集元素,可使用构造表达式,以投影属性对创建的类实例进行初始化。

44.9.1.5. SELECT子句中的构造表达式

在SELECT列表中允许使用一个构造表达式来创建并返回一个类的实例。这个类不需要是实体类。引用构造器时必须使用全路径类名。构造表达式通常结合投影一起使用,返回以投影属性进行初始化的类实例。

SELECT NEW com.acme.example.CustomerDetails(c.id, c.status, o.count)
FROM Customer c JOIN c.orders o
WHERE o.count > 100

若在构造表达式中指定了实体类,则返回的实体实例将处于新建状态(参考第 44.8.4.2 节 “管理实体实例的生存周期”)。

44.9.1.6. 子查询

在WHERE子句与HAVING子句中可以使用子查询。EJB3.0规范暂不支持在FROM子句中使用子查询。

子查询的BNF语法结构如下:

subquery ::= simple_select_clause subquery_from_clause [where_clause]
               [groupby_clause] [having_clause]
simple_select_clause ::= SELECT [DISTINCT] simple_select_expression
subquery_from_clause ::=
       FROM subselect_identification_variable_declaration
               {, subselect_identification_variable_declaration}*
subselect_identification_variable_declaration ::=
       identification_variable_declaration |
       association_path_expression [AS] identification_variable |
       collection_member_declaration
simple_select_expression::=
       single_valued_path_expression |
       aggregate_expression |
       identification_variable

例如:

SELECT DISTINCT emp
FROM Employee emp
WHERE EXISTS (
  SELECT spouseEmp
  FROM Employee spouseEmp
  WHERE spouseEmp = emp.spouse)

SELECT c
FROM Customer c
WHERE (SELECT COUNT(o) FROM c.orders o) > 10

注意在某些场合下,要求子查询的结果必须为单值。例如:

SELECT goodCustomer
FROM Customer goodCustomer
WHERE goodCustomer.balanceOwed < (
  SELECT avg(c.balanceOwed) FROM Customer c)

44.9.1.7. 查询中的命名参数

在查询中可使用冒号(:)前缀定义命名参数。关于命名参数详情可参考第 44.8.4.4 节 “查询中的命名参数”

44.9.2. Java持久查询语言BNF

标记概述

{ ... } 分组
[ ... ] 可选结构
粗体 关键字
* 出现不定次数 (包括零次或一次)
| 选择项目

Java持久查询语言的完整BNF定义:

QL_statement ::= select_statement | update_statement | delete_statement
select_statement ::= select_clause from_clause [where_clause] [groupby_clause]
        [having_clause] [orderby_clause]
update_statement ::= update_clause [where_clause]
delete_statement ::= delete_clause [where_clause]
from_clause ::=
        FROM identification_variable_declaration
                {, {identification_variable_declaration | collection_member_declaration}}*
identification_variable_declaration ::= range_variable_declaration { join | fetch_join }*
range_variable_declaration ::= abstract_schema_name [AS] identification_variable
join ::= join_spec join_association_path_expression [AS] identification_variable
fetch_join ::= join_spec FETCH join_association_path_expression
association_path_expression ::=
        collection_valued_path_expression | single_valued_association_path_expression
join_spec::= [ LEFT [OUTER] | INNER ] JOIN
join_association_path_expression ::= join_collection_valued_path_expression |
        join_single_valued_association_path_expression
join_collection_valued_path_expression::=
        identification_variable.collection_valued_association_field
join_single_valued_association_path_expression::=
        identification_variable.single_valued_association_field
collection_member_declaration ::=
        IN (collection_valued_path_expression) [AS] identification_variable
single_valued_path_expression ::=
        state_field_path_expression | single_valued_association_path_expression
state_field_path_expression ::=
        {identification_variable | single_valued_association_path_expression}.state_field
single_valued_association_path_expression ::=
identification_variable.{single_valued_association_field.}* single_valued_association_field
collection_valued_path_expression ::=
identification_variable.{single_valued_association_field.}*collection_valued_association_field
state_field ::= {embedded_class_state_field.}*simple_state_field
update_clause ::= UPDATE abstract_schema_name [[AS] identification_variable]
                     SET update_item {, update_item}*
update_item ::= [identification_variable.]{state_field | single_valued_association_field} =
                     new_value
new_value ::=
       simple_arithmetic_expression |
       string_primary |
       datetime_primary |
       boolean_primary |
       enum_primary
       simple_entity_expression |
       NULL
delete_clause ::= DELETE FROM abstract_schema_name [[AS] identification_variable]
select_clause ::= SELECT [DISTINCT] select_expression {, select_expression}*
select_expression ::=
        single_valued_path_expression |
        aggregate_expression |
        identification_variable |
        OBJECT(identification_variable) |
        constructor_expression
constructor_expression ::=
        NEW constructor_name ( constructor_item {, constructor_item}* )
constructor_item ::= single_valued_path_expression | aggregate_expression
aggregate_expression ::=
       { AVG | MAX | MIN | SUM } ([DISTINCT] state_field_path_expression) |
       COUNT ([DISTINCT] identification_variable | state_field_path_expression |
              single_valued_association_path_expression)
where_clause ::= WHERE conditional_expression
groupby_clause ::= GROUP BY groupby_item {, groupby_item}*
groupby_item ::= single_valued_path_expression | identification_variable
having_clause ::= HAVING conditional_expression
orderby_clause ::= ORDER BY orderby_item {, orderby_item}*
orderby_item ::= state_field_path_expression [ ASC | DESC ]
subquery ::= simple_select_clause subquery_from_clause [where_clause]
                [groupby_clause] [having_clause]
subquery_from_clause ::=
       FROM subselect_identification_variable_declaration
              {, subselect_identification_variable_declaration}*
subselect_identification_variable_declaration ::=
       identification_variable_declaration |
       association_path_expression [AS] identification_variable |
       collection_member_declaration
simple_select_clause ::= SELECT [DISTINCT] simple_select_expression
simple_select_expression::=
       single_valued_path_expression |
       aggregate_expression |
       identification_variable
conditional_expression ::= conditional_term | conditional_expression OR conditional_term
conditional_term ::= conditional_factor | conditional_term AND conditional_factor
conditional_factor ::= [ NOT ] conditional_primary
conditional_primary ::= simple_cond_expression | (conditional_expression)
simple_cond_expression ::=
       comparison_expression |
       between_expression |
       like_expression |
       in_expression |
       null_comparison_expression |
       empty_collection_comparison_expression |
       collection_member_expression |
       exists_expression
between_expression ::=
       arithmetic_expression [NOT] BETWEEN
               arithmetic_expression AND arithmetic_expression |
       string_expression [NOT] BETWEEN string_expression AND string_expression
       datetime_expression [NOT] BETWEEN
               datetime_expression AND datetime_expression
in_expression ::=
       state_field_path_expression [NOT] IN ( in_item {, in_item}* | subquery)
in_item ::= literal | input_parameter
like_expression ::=
         string_expression [NOT] LIKE pattern_value [ESCAPE escape_character]
null_comparison_expression ::=
         {single_valued_path_expression | input_parameter} IS [NOT] NULL
empty_collection_comparison_expression ::=
         collection_valued_path_expression IS [NOT] EMPTY
collection_member_expression ::= entity_expression
               [NOT] MEMBER [OF] collection_valued_path_expression
exists_expression::= [NOT] EXISTS (subquery)
all_or_any_expression ::= { ALL | ANY | SOME} (subquery)
comparison_expression ::=
        string_expression comparison_operator {string_expression | all_or_any_expression} |
        boolean_expression { =|<>} {boolean_expression | all_or_any_expression} |
        enum_expression { =|<>} {enum_expression | all_or_any_expression} |
        datetime_expression comparison_operator
                {datetime_expression | all_or_any_expression} |
        entity_expression { = | <> } {entity_expression | all_or_any_expression} |
        arithmetic_expression comparison_operator
                {arithmetic_expression | all_or_any_expression}
comparison_operator ::= = | > | >= | < | <= | <>
arithmetic_expression ::= simple_arithmetic_expression | (subquery)
simple_arithmetic_expression ::=
        arithmetic_term | simple_arithmetic_expression { + | - } arithmetic_term
arithmetic_term ::= arithmetic_factor | arithmetic_term { * | / } arithmetic_factor
arithmetic_factor ::= [{ + | - }] arithmetic_primary
arithmetic_primary ::=
        state_field_path_expression |
        numeric_literal |
        (simple_arithmetic_expression) |
        input_parameter |
        functions_returning_numerics |
        aggregate_expression
string_expression ::= string_primary | (subquery)
string_primary ::=
        state_field_path_expression |
        string_literal |
        input_parameter |
        functions_returning_strings |
        aggregate_expression
datetime_expression ::= datetime_primary | (subquery)
datetime_primary ::=
        state_field_path_expression |
        input_parameter |
        functions_returning_datetime |
        aggregate_expression
boolean_expression ::= boolean_primary | (subquery)
boolean_primary ::=
       state_field_path_expression |
       boolean_literal |
       input_parameter |
enum_expression ::= enum_primary | (subquery)
enum_primary ::=
      state_field_path_expression |
      enum_literal |
      input_parameter |
entity_expression ::=
      single_valued_association_path_expression | simple_entity_expression
simple_entity_expression ::=
      identification_variable |
      input_parameter
functions_returning_numerics::=
      LENGTH(string_primary) |
      LOCATE(string_primary, string_primary[, simple_arithmetic_expression]) |
      ABS(simple_arithmetic_expression) |
      SQRT(simple_arithmetic_expression) |
      MOD(simple_arithmetic_expression, simple_arithmetic_expression) |
      SIZE(collection_valued_path_expression)
functions_returning_datetime ::=
      CURRENT_DATE|
      CURRENT_TIME |
      CURRENT_TIMESTAMP
functions_returning_strings ::=
      CONCAT(string_primary, string_primary) |
      SUBSTRING(string_primary,
           simple_arithmetic_expression, simple_arithmetic_expression)|
      TRIM([[trim_specification] [trim_character] FROM] string_primary) |
      LOWER(string_primary) |
      UPPER(string_primary)
trim_specification ::= LEADING | TRAILING | BOTH