Java 持久性/JPQL
Java 持久性查询语言 (JPQL) 是 JPA 定义的查询语言。JPQL 类似于 SQL,但它作用于对象、属性和关系,而不是表和列。JPQL 可用于读取 (SELECT),以及批量更新 (UPDATE) 和删除 (DELETE)。JPQL 可用于NamedQuery(通过注解或 XML) 或在使用EntityManager createQuery()API 的动态查询中。
有关 JPQL BNF,请参阅 BNF。
选择查询可用于从数据库读取对象。选择查询可以返回单个对象或数据元素、对象或数据元素列表,或包含多个对象和数据的对象数组。
// Query for a List of objects.
Query query = em.createQuery("Select e FROM Employee e WHERE e.salary > 100000");
List<Employee> result = query.getResultList();
// Query for a single object.
Query query = em.createQuery("Select e FROM Employee e WHERE e.id = :id");
query.setParameter("id", id);
Employee result2 = (Employee)query.getSingleResult();
// Query for a single data element.
Query query = em.createQuery("Select MAX(e.salary) FROM Employee e");
BigDecimal result3 = (BigDecimal)query.getSingleResult();
// Query for a List of data elements.
Query query = em.createQuery("Select e.firstName FROM Employee e");
List<String> result4 = query.getResultList();
// Query for a List of element arrays.
Query query = em.createQuery("Select e.firstName, e.lastName FROM Employee e");
List<Object[]> result5 = query.getResultList();
SELECT 子句可以包含对象表达式、属性表达式、函数、子查询、构造函数和聚合函数。
聚合函数可以包含关于一组对象的摘要信息。这些函数包括 MIN、MAX、AVG、SUM、COUNT。这些函数可以用于返回单个结果,或者与 GROUP BY 结合使用以返回多个结果。
SELECT COUNT(e) FROM Employee e
SELECT MAX(e.salary) FROM Employee e
NEW 运算符可以与完全限定的类名一起使用,以从 JPQL 查询返回数据对象。这些不会是托管对象,并且该类必须定义一个与构造函数的参数及其类型匹配的构造函数。构造函数查询可用于选择对象上的部分数据或报告数据,并获取类实例而不是对象数组。
SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e
FROM 子句定义要查询的内容。典型的 FROM 子句将包含要查询的实体名称并为其分配别名。
SELECT e FROM Employee e
JPQL 允许查询多个根级对象。在执行此操作时应谨慎,因为它会导致两个表的笛卡尔积。WHERE 子句应确保以某种方式连接这两个对象。
SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address
JPQL 中使用的实体名称来自 @Entity 注解或 XML 的 name 属性。它默认为简单的实体类名。一些 JPA 提供者还允许使用完整类名。
- EclipseLink : TopLink - 允许使用实体的完全限定类名。
JOIN 子句也可以在 FROM 子句中使用。JOIN 子句允许将对象的任何关系连接到查询中,以便它们可以在 WHERE 子句中使用。JOIN 不意味着关系将被获取,除非包含 FETCH 选项。
SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city
JOIN 可与 OneToOne、ManyToOne、OneToMany、ManyToMany 和 ElementCollection 映射一起使用。与集合关系一起使用时,您可以多次连接相同的关系以查询多个独立值。
SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 and p2.name = :p2
FETCH 选项可用于 JOIN 以在单个查询中获取相关对象。这避免了对每个对象关系的额外查询,并确保在关系为 LAZY 时已获取它们。
SELECT e FROM Employee e JOIN FETCH e.address
JOIN FETCH 不允许在 JPA 规范中使用别名,但一些 JPA 提供者可能允许它。
- EclipseLink : TopLink - 允许使用别名。
默认情况下,JPQL 中的所有连接都是 INNER 连接。这意味着没有关系的结果将从查询结果中过滤掉。要避免这种情况,可以使用 LEFT 选项将连接定义为 OUTER 连接。
SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city
用于连接的连接条件来自映射的连接列。这意味着 JPQL 用户通常可以不必了解每个关系是如何连接的。在某些情况下,需要将附加条件附加到连接条件,通常是在外部连接的情况下。这可以通过 ON 子句来完成。ON 子句在 JPA 2.1 规范中定义,一些 JPA 提供者可能支持它。
- EclipseLink : Hibernate : TopLink - 支持 ON 子句。
SELECT e FROM Employee e LEFT JOIN e.address a ON a.city = :city
FROM 子句中的子查询
[edit | edit source]JPA 不支持 FROM 子句中的子查询。某些 JPA 提供商可能支持此功能。
- EclipseLink : TopLink - 支持 FROM 子句中的子查询。
ORDER BY 子句
[edit | edit source]ORDER BY 允许指定结果的排序。可以对多个值进行排序,按升序 (ASC) 或降序 (DESC)。JPA 1.0 和 2.0 BNF 限制了在 ORDER BY 中使用函数和子查询,但 JPA 2.1 草案删除了其中大部分限制。
- EclipseLink : TopLink - 允许在 ORDER BY 子句中使用函数、子查询、NULLS FIRST/LAST、对象表达式和其他操作。
SELECT e FROM Employee e ORDER BY e.lastName ASC, e.firstName, ASC
SELECT e FROM Employee e ORDER BY UPPER(e.lastName)
GROUP BY 子句
[edit | edit source]GROUP BY 允许对一组对象计算汇总信息。GROUP BY 通常与聚合函数一起使用。
SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city
SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city ORDER BY AVG(e.salary)
SELECT e, COUNT(p) FROM Employee e LEFT JOIN e.projects p GROUP BY e
HAVING 子句
[edit | edit source]HAVING 子句允许对 GROUP BY 的结果进行过滤。
SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city HAVING AVG(e.salary) > 100000
UNION
[edit | edit source]JPA 不支持 SQL UNION、INTERSECT 和 EXCEPT 操作。某些 JPA 提供商可能支持这些操作。
- EclipseLink : TopLink - 支持 UNION、INTERSECT 和 EXCEPT。
WHERE 子句
[edit | edit source]WHERE 子句通常是查询的主要部分,因为它定义了过滤返回内容的条件。WHERE 子句可以使用任何比较运算符、逻辑运算符、函数、属性、对象和子查询。比较运算符包括 =、<、>、<=、>=、<>、LIKE、BETWEEN、IS NULL 和 IN。NOT 也可与任何比较运算符一起使用 (NOT LIKE、NOT BETWEEN、IS NOT NULL、NOT IN)。逻辑运算符包括 AND、OR 和 NOT。
比较运算符
[edit | edit source]操作 | 描述 | 示例 |
---|---|---|
= | 等于 | e.firstName = 'Bob'
|
< | 小于 | e.salary < 100000
|
> | 大于 | e.salary > :sal
|
<= | 小于或等于 | e.salary <= 100000
|
>= | 大于或等于 | e.salary >= :sal
|
LIKE | 评估两个字符串是否匹配,'%' 和 '_' 是有效的通配符,并且 ESCAPE 字符是可选的 | e.firstName LIKE 'A%' OR e.firstName NOT LIKE '%._%' ESCAPE '.'
|
BETWEEN | 评估该值是否在两个值之间 | e.firstName BETWEEN 'A' AND 'C'
|
IS NULL | 将该值与 null 进行比较,数据库可能不允许或在使用 = 与 null 时产生意外的结果 | e.endDate IS NULL
|
IN | 评估该值是否包含在列表中 | e.firstName IN ('Bob', 'Fred', 'Joe')
|
IN 操作允许使用值或参数列表、单个列表参数或子查询。
e.firstName IN (:name1, :name2, :name3)
e.firstName IN (:name1)
e.firstName IN :names
e.firstName IN (SELECT e2.firstName from Employee e2 WHERE e2.lastName = 'Smith')
子查询可与任何操作一起使用,前提是它返回单个值,或使用 ALL 或 ANY 选项。ALL 表示操作必须对子查询返回的所有元素都为真,ANY 表示操作必须对子查询返回的任何元素都为真。
e.firstName = (SELECT e2.firstName from Employee e2 WHERE e2.id = :id)
e.salary < (SELECT e2.salary from Employee e2 WHERE e2.id = :id)
e.firstName = ANY (SELECT e2.firstName from Employee e2 WHERE e.id <> e.id)
e.salary <= ALL (SELECT e2.salary from Employee e2)
更新查询
[edit | edit source]可以使用UPDATE语句对实体进行批量更新。该语句对单个实体类型进行操作,并设置实体的单个值属性,这些属性受WHERE子句中的条件约束。更新查询提供等效于SQL UPDATE语句的内容,但使用 JPQL 条件表达式。
更新查询不允许联接,但支持子查询。可以在 WHERE 子句中遍历 OneToOne 和 ManyToOne 关系。仍可以通过在 WHERE 子句中使用包含子查询的 EXISTS 来查询集合关系。更新查询只能更新对象的属性或其嵌入式属性,不能更新其关系。复杂更新查询取决于数据库的更新支持,并且可能在某些数据库上使用临时表。
更新查询应仅用于批量更新,对对象的常规更新应通过在事务中使用对象的 set 方法并提交更改来完成。
更新查询返回数据库中修改的行数(行计数)。
此示例演示如何使用更新查询为员工加薪。该WHERE子句包含条件表达式。
更新查询示例
[edit | edit source]Query query = em.createQuery("UPDATE Employee e SET e.salary = 60000 WHERE e.salary = 50000");
int rowCount = query.executeUpdate();
持久性上下文不会更新以反映更新操作的结果。如果使用事务范围的持久性上下文,则应将批量操作在单独的事务中执行,或者使其成为事务中的第一个操作。这是因为持久性上下文积极管理的任何实体将不知道数据库级别发生的实际更改。
与更新查询匹配的共享缓存中的对象将失效,以确保后续持久性上下文看到更新后的数据。
删除查询
[edit | edit source]可以使用DELETE语句对实体进行批量删除。删除查询提供等效于SQL DELETE语句的内容,但使用 JPQL 条件表达式。
删除查询不允许联接,但支持子查询。可以在 WHERE 子句中遍历 OneToOne 和 ManyToOne 关系。仍可以通过在 WHERE 子句中使用包含子查询的 EXISTS 来查询集合关系。复杂删除查询取决于数据库的删除支持,并且可能在某些数据库上使用临时表。
删除查询应仅用于批量删除,对对象的常规删除应通过调用EntityManager remove()API 的动态查询中。
删除查询返回数据库中删除的行数(行计数)。
此示例演示如何使用删除查询来删除未分配给部门的所有员工。该WHERE子句包含条件表达式。
删除查询示例
[edit | edit source]Query query = em.createQuery("DELETE FROM Employee e WHERE e.department IS NULL");
int rowCount = query.executeUpdate();
删除查询是多态的:满足删除查询条件的任何实体子类实例都将被删除。但是,删除查询不遵守级联规则:除查询中引用的类型及其子类之外,不会删除任何其他实体,即使实体与其他具有启用级联删除的实体具有关系也是如此。删除查询将从联接表和集合表中删除行。
持久性上下文可能不会更新以反映删除操作的结果。如果使用事务范围的持久性上下文,则应将批量操作在单独的事务中执行,或者使其成为事务中的第一个操作。这是因为持久性上下文积极管理的任何实体将不知道数据库级别发生的实际更改。
参数
[edit | edit source]JPA 定义了命名参数和位置参数。命名参数可以在 JPQL 中使用语法:<name>指定。位置参数可以在 JPQL 中使用语法?或?<position>指定。位置参数从位置1而不是0.
命名参数查询示例
[edit | edit source]Query query = em.createQuery("SELECT e FROM Employee e WHERE e.firstName = :first and e.lastName = :last");
query.setParameter("first", "Bob");
query.setParameter("last", "Smith");
List<Employee> list = query.getResultList();
位置参数查询示例
[edit | edit source]Query query = em.createQuery("SELECT e FROM Employee e WHERE e.firstName = ?1 and e.lastName = ?2");
query.setParameter(1, "Bob");
query.setParameter(2, "Smith");
List<Employee> list = query.getResultList();
文字
[edit | edit source]文字值可以在 JPQL 中内联以用于标准 Java 类型。通常,使用参数而不是内联值会更好。内联参数会阻止 JPQL 从 EclipseLink 的 JPQL 解析器缓存中受益,并且可能会使应用程序容易受到 JPQL 注入攻击。
每个 Java 类型都定义了自己的内联语法
String - '<string>'
SELECT e FROM Employee e WHERE e.name = 'Bob'
- 要在一个字符串中定义一个'(引号)字符,需要将引号用双引号括起来,例如:
'Baie-D''Urfé'
。
- 要在一个字符串中定义一个'(引号)字符,需要将引号用双引号括起来,例如:
整数 - +|-<数字>
SELECT e FROM Employee e WHERE e.id = 1234
长整型 - +|-<数字>L
SELECT e FROM Employee e WHERE e.id = 1234L
浮点型 - +|-<数字>.<小数部分><指数>F
SELECT s FROM Stat s WHERE s.ratio > 3.14F
双精度浮点型 - +|-<数字>.<小数部分><指数>D
SELECT s FROM Stat s WHERE s.ratio > 3.14e32D
布尔型 - TRUE | FALSE
SELECT e FROM Employee e WHERE e.active = TRUE
日期 - {d'yyyy-mm-dd'}
SELECT e FROM Employee e WHERE e.startDate = {d'2012-01-03'}
时间 - {t'hh:mm:ss'}
SELECT e FROM Employee e WHERE e.startTime = {t'09:00:00'}
时间戳 - {ts'yyyy-mm-dd hh:mm:ss.nnnnnnnnn'}
-SELECT e FROM Employee e WHERE e.version = {ts'2012-01-03 09:00:00.000000001'}
枚举 - 包名.类名.枚举名
SELECT e FROM Employee e WHERE e.gender = org.acme.Gender.MALE
空值 - NULL
UPDATE Employee e SET e.manager = NULL WHERE e.manager = :manager
JPQL 支持多种数据库函数。这些函数在名称和语法上独立于数据库,但需要数据库支持。如果数据库支持等效的函数或不同的语法,则支持标准 JPQL 函数;如果数据库没有提供任何执行该函数的方法,则不支持该函数。对于数学函数(+、-、/、*),遵循 BEDMAS 规则。一些 JPA 提供者可能支持其他函数。
- EclipseLink : TopLink - 也支持 CAST、EXTRACT 和 REGEXP。
函数 | 描述 | 示例 |
---|---|---|
- | 减法 | e.salary - 1000
|
+ | 加法 | e.salary + 1000
|
* | 乘法 | e.salary * 2
|
/ | 除法 | e.salary / 2
|
ABS | 绝对值 | ABS(e.salary - e.manager.salary)
|
CASE | 定义一个 case 语句 | CASE e.status WHEN 0 THEN 'active' WHEN 1 THEN 'consultant' ELSE 'unknown' END
|
COALESCE | 评估为第一个非空参数值 | COALESCE(e.salary, 0)
|
CONCAT | 连接两个或多个字符串值 | CONCAT(e.firstName, ' ', e.lastName)
|
CURRENT_DATE | 数据库上的当前日期 | CURRENT_DATE
|
CURRENT_TIME | 数据库上的当前时间 | CURRENT_TIME
|
CURRENT_TIMESTAMP | 数据库上的当前日期时间 | CURRENT_TIMESTAMP
|
LENGTH | 字符或二进制值的字符/字节长度 | LENGTH(e.lastName)
|
LOCATE | 字符串在字符串中的索引,可以选择从起始索引开始 | LOCATE('-', e.lastName)
|
LOWER | 将字符串值转换为小写 | LOWER(e.lastName)
|
MOD | 计算第一个整数除以第二个整数的余数 | MOD(e.hoursWorked / 8)
|
NULLIF | 如果第一个参数等于第二个参数,则返回 null,否则返回第一个参数 | NULLIF(e.salary, 0)
|
SQRT | 计算数字的平方根 | SQRT(o.result)
|
SUBSTRING | 从字符串中获取子字符串,从索引开始,可以选择子字符串大小 | SUBSTRING(e.lastName, 0, 2)
|
TRIM | 从字符串中修剪前导、尾随或同时修剪空格或可选修剪字符 | TRIM(TRAILING FROM e.lastName), TRIM(e.lastName), TRIM(LEADING '-' FROM e.lastName)
|
UPPER | 将字符串值转换为大写 | UPPER(e.lastName)
|
JPQL 定义了几个特殊的运算符,它们不是数据库函数,但在 JPQL 中具有特殊含义。这些包括 INDEX、KEY、SIZE、IS EMPTY、TYPE、FUNCTION 和 TREAT。
- EclipseLink : TopLink - 也支持 FUNCTION、TREAT、FUNC、OPERATOR、SQL、COLUMN 和 TABLE。
函数 | 描述 | 示例 |
---|---|---|
INDEX | 有序列表元素的索引,仅支持 @OrderColumn | SELECT toDo FROM Employee e JOIN e.toDoList toDo WHERE INDEX(toDo) = 1
|
KEY | Map 元素的键 | SELECT p FROM Employee e JOIN e.priorities p WHERE KEY(p) = 'high'
|
SIZE | 集合关系的大小,这将评估为一个子查询 | SELECT e FROM Employee e WHERE SIZE(e.managedEmployees) < 2
|
IS EMPTY, IS NOT EMPTY | 如果集合关系为空,则评估为 true,这将评估为一个子查询 | SELECT e FROM Employee e WHERE e.managedEmployees IS EMPTY
|
MEMBER OF, NOT MEMBER OF | 如果集合关系包含该值,则评估为 true,这将评估为一个子查询 | SELECT e FROM Employee e WHERE 'write code' MEMBER OF e.responsibilities
|
TYPE | 继承鉴别器值 | SELECT p FROM Project p WHERE TYPE(p) = LargeProject
|
TREAT | 将对象视为其子类值(JPA 2.1 草案) | SELECT e FROM Employee JOIN TREAT(e.projects as LargeProject) p WHERE p.budget > 1000000
|
FUNCTION | 调用数据库函数(JPA 2.1 草案) | SELECT p FROM Phone p WHERE FUNCTION('TO_NUMBER', p.areaCode) > 613
|