JPQL支持函数功能,多种的IN
,LIKE
和BETWEEN
样式表达式,及面向集合(collection)的条件表达式。这一节详细讨论写查询可用的各种选择。
一个查询语句中操作符的优先级为:
-
导航操作符(.)
-
一元符号(+,-)
-
乘(*),除(/)
-
加(+),减(-)
-
比较操作符,=, >, >=, <, <=,<> (不等), [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF]
-
逻辑操作符,NOT, AND, OR
你可以用BETWEEN
操作符指定一个实体字段的范围。BETWEEN
的语法是:
expression [NOT] BETWEEN expression AND expression
这里expression
可以是一个字符串,算术或日期时间表达式。这里有几个使用BETWEEN
操作符的实例。
SELECT u FROM User u WHERE u.dateCreated between :startDate AND :endDate
SELECT t FROM Topic t WHERE t.postCount NOT BETWEEN ?1 AND ?2IN
你可以利用IN
比较操作符指定为一个状态字段指定一系列的值。你可以列出一个或多个字符串或参数值(基于位置或命名的),或者利用子查询动态的生成一系列的值。字符型,数字型,枚举型的状态字段可以用在IN
操作符上。状态字段的类型必须与列表中的值的类型一致。IN
操作符的语法为:
state-field [NOT] in (item {, item2}* | subquery).
这里有几个例子。
SELECT f FROM Forum f WHERE f.type IN (?1, ?2)
SELECT f FROM Forum f WHERE f.type IN (1, 2)LIKE
LIKE
允许你根据部分值搜索字符串字段。JPQL用一个下划线(_)表示你搜索字符字符串中任一字符。在查询语句中可以用百分号(%)表示一系列字符,其它的字符代表他们本身。LIKE
的一般格式为:
string-expression [NOT] LIKE pattern [ESCAPE escape-char]
如果你必须在查询语句中使用下划线或百分号作为字面字符,使用ESCAPE
格式。例如,你可以用forum.description like ‘QA\_%’ ESCAPE ‘\’
。你必须在下划线或百分号前加入反反斜线符号人,并且在搜索字符串后面加入ESCAPE ‘\’
语法。这里列出几个例子:
-
‘tr_ck’可以匹配‘truck’和‘trick’, 但不能匹配‘trucker’。
-
‘tr%’可以匹配‘truck’, ‘tractor’, ‘trick’, 等等。
-
‘tr_ck%’可以匹配‘truck’, ‘trick’, 和‘trucker’。
如果你想搜索字符串_hello,你的查询语句应该是这样的:
‘\_hello’ ESCAPE ‘\’
ESCAPE ‘\’
告诉数据库,“我正在一个转义字符('\')上使用反斜线”。下面用代码表示:
em.createQuery("SELECT f FROM Forum f " +
"WHERE f.description LIKE '\\_%' ESCAPE '\\'");
在这段代码中,你使用了两个反斜线。第一个是为了Java编译器,第二个是由于JPQL解析器。
如果在MySQL数据库上执行上面的ESCAPE
查询语句,你可能会得到一个数据库异常。默认情况下,MySQL会将反斜线识别成一个转义符号,所以它告诉你像处理转义符号那样处理反斜线是错误的。为了使你的查询能在不同数据库之间进行移植。你必须在JDBC连接中对所有MySQL关闭所有MySQL数据库实例上的反斜线转义功能。要在你的JDBC连接上禁用反斜线转义,将下面的URL中的sessionVariables
部分添加到你的JDBC连接中。
jdbc:mysql://localhost:3306/db?sessionVariables=sql_mode=NO_BACKSLASH_ESCAPES
更多信息,请参考MySQL文档。
IS NULLIS NULL
比较操作符能够让检测NULL
字段,不管是单值路径表达式还是输入参数。你可以使用IS NOT NULL
来确保一个单值路径表达式有非空值,或者使用IS NULL
来检测NULL
值。
SELECT p FROM PrivateMessage p WHERE p.dateRead IS NOT NULL
// toUser references a many-to-one relationship, so you can use IS [NOT] NULL
SELECT p FROM PrivateMessage p WHERE p.toUser IS NOT NULL
// this query does not work because we are using a
// collection-value path-expression
SELECT f FROM Forum f WHERE f.topics IS NULLIS EMPTY
IS [NOT] EMPTY
操作用于空或者非空的集合值表达式。
// the above query rewritten to use IS EMPTY
SELECT f FROM Forum f WHERE f.topics IS EMPTY
// this query will find all forum entities with topics
// (i.e. the collection is not empty)
SELECT f FROM Forum f WHERE f.topics IS NOT EMPTYMEMBER
可以用[NOT] MEMBER [OF]
来判断一个实体是否是一个集合的一部分。[OF]
是可选的,不影响MEMBER
比较操作符。你可用可不用。
可以用NOT MEMBER
来判断一个实体不是一个集合的组成部分。MEMBER
的语法如下:
Expression [NOT] MEMBER [OF] collection-valued path-expression
// find the forum instance that contains Topic t
Query q2 = em.createQuery("SELECT f FROM Forum f " +
"WHERE :topic MEMBER f.topics");
q2.setParameter("topic", t);
List results2 = q2.getResultList();字符串函数(STRING FUNCTIONS)
JPQL查询的WHERE
或HAVING
语句中支持字符串函数作为函数表达式。
-
CONCAT(string 1, string 2)
:将字符串2追加到字符串1。 -
SUBSTRING(string, starting position, length)
:从字符串string
开始位置starting position
截取长度为length
字符。 -
LOWER(string)
:将一个字符串string
转换成小写形式。 -
UPPER(string)
:将一个字符串string
转换成大写形式。 -
LENGTH(string)
:返回字符串string
的长度,为整数。 -
TRIM([[LEADING|TRAILING|BOTH] [char] FROM] string)
:去掉字符串string
头,尾或两者的字符char
。最简形式是TRIM(string)
,可以去掉字符串string
头尾的空格字符。 -
LOCATE(string1, string2 [,start])
:返回string2
在string1
的位置。定位函数有一个可选的起始位置start
。
JPQL查询的WHERE
或HAVING
语句中支持数学函数作为函数表达式。
-
ABS(arithmetic expression)
:返回算术表达式的绝对值。 -
SQRT(arithmetic expression)
:求算术表达式的方根,返回一个Double。 -
MOD(arithmetic expression 1, arithmetic expression 2)
:求参数1与参数2的模,返回一个整数。 -
SIZE(collection-valued path-expression)
:计算一个集合中元素的数量,并返回一个整数。如果集合为空,返回0。
支持以下时间函数。
-
CURRENT_DATE
:当时日期,由数据库决定。 -
CURRENT_TIME
:当时时间,由数据库决定。 -
CURRENT_TIMESTAMP
:当时日期和时间,由数据库决定
SECLECT函数
SELECT
语句标识查询结果。SELECT
语句包含一个或多个下列元素。
-
一个路径表达式或是标识变量:表明返回一个实体。
-
一个单值路径表达式:指定返回一个字段或实体。
-
一个统计SELECT表达式:表明返回计算结果(如,
COUNT(*)
)。 -
一个构造器表达式:允许你从选择的条目中返回一个对象。
SELECT
语句允许查询各种实体,计算结果,投影值,非实体类。你可以在SELECT
语句中使用集合值的路径表达式,然而,下面表达是非法的。
SELECT f.topics FROM Forum f
前面已经提供,一些JPA的实现允许这类的查询。为了保持移植性,你应该使用下面的语句替换(参看Joins
一节)。
SELECT t FROM Forum f JOIN f.topics t
查询的结果可以是一个抽象模型类型,一个状态字段(实体的字段或属性),一个统计函数的结果,由NEW
操作符创建的对象,或任何它们可能的组合。如果你查询一个抽象模型类型或是构建一个新的对象,查询结果会是实体类型的对象组成的列表或一个新对象。如果你使用了统计函数,查询状态字段,或是不同的类型,返回结果是一个数组(Object[])实例的列表。数组中对象的位置与你在查询语句中指定的位置一致。例如。
SELECT t.subject, t.content FROM Topic t
这个查询返回一个数组(Object[])实例组成的列表。这个列表中每个项目包含两个String
对象,第一项目(index 0)是标题,第二个(index 1)是主题的内容。
你可以创建一个新的对象作为查询结果。这个对象不要求是实体,但要有一个构造器,它的顺序与类型与SELECT
语句一致。下面是一个封装用户统计数据的临时对象。
private String username;
private Integer userId;
private long postCount;
public UserStatistics(String username, Integer userId, long postCount) {
super();
this.username = username;
this.userId = userId;
this.postCount = postCount;
}
// getter methods removed for readability
}
下面的查询语句会计算系统中每个用户的发帖数量,并将结果,用户名,用户ID保存到一个临时对象UserStatistics
中。
+"u.username, u.id, COUNT(p)) “
+ “FROM Post p JOIN p.createdByUser u "
+ "WHERE p.parent IS NOT NULL GROUP BY u");
List results = q.getResultList();统计函数(AGGREGATE FUNCTIONS)
SELECT
语句中可以使用以下统计函数(在一个路径表达式上应用)。
计算查询结果返回的数值型参数的平均值,并返回一个Double
整型。
计算所找到的实体的总和,并返回一个Long
整型。如果没有找到实体,COUNT
返回0。
计算查询结果作为参数的最大值,返回类型与参数类型一致。MAX
函数可以应用到任何可排序的状态字段上,包括数字类型,字符串,字符类型,或日期。
计算查询结果作为参数的最小值,返回类型与参数类型一致。MIN
函数可以应用到任何可排序的状态字段上,包括数字类型,字符串,字符类型,或日期。
计算查询结果作为数值型参数的总和,当参数是浮点类型时返回一个Double
类型,当使用BigInteger
返回一个BigInteger
,当参数为BigDecimal
时返回BigDecimal
。
除COUNT
外,这些函数必须用在以状态字段结尾的路径表达式上。你可以用一个状态字段,关联字段,或是一个标志符号变量作为参数用在COUNT
函数。
如果SUM, AVG, MIN,和MAX计算的值不存在时,返回一个NULL
。
为了避免使用统计函数查询结果的重复,请使用DISTINCT
操作符。但是在MAX 和MIN使用DISTINCT
是非法的。另外,在函数计算结果之前已经剔除了NULL
值,不管你是否使用了DISTINCT
。
当使用一个构造器表达式组成的SELECT
语句,函数的返回类型应该注意。UserAverages
对象的postCount
属性是一个Long
对象。最初,使用一个int
,查询语句运行时,Hibernate会一个IllegalArgumentException
异常,表明UserStatistics
与构造器不相符。我意识到COUNT
返回的是一个Long
类型对象,更新一下UserStatistics
,查询语句就可以正确的运行。
排序(ORDER BY)
ORDER BY
允许你对查询结果进行排序。数据库排序比应用程序更有效。要依据集合的顺序,可以使用ORDER BY
操作符。(更多信息参见第三章Entities Part II中@OrderBy
注释)
这是一个JPQL查询语句中Order By
操作符的所在位置。
Order By
的语法为:
下面是一些合法的Order By
实例。
-
SELECT u FROM User u ORDER BY u
-
SELECT u FROM User u ORDER BY u.address
-
SELECT u.username, u.id FROM User u ORDER BY u.username DESC
expression
可以是一个标识符号变量,一个单值关联路径,一个状态字段路径表达式。这些查询语句一一演示了Order By
的这些类型。
你必须注意使用Order By
操作符的一些限制。
-
当使用一个标识符号变量,一个单值关联字段时,你用来对查询进行排序的项目必须是一个可排序的类型,例如数字类型,字符串,字符类型,或是时间日期。
-
如果你使用一个状态字段路径表达式,同时它也必须出现在
SELECT
语句中。
ASC
表示升序(由小到大),是默认的排序方式。DESC
表示降序排列(由大到小),使用时显式在查询的Order By
语句上添加它。排序的优先级是按罗列的字段从左到右的顺序进行。
不难理解,下面的查询是不合法的,因为它对一个字段进行排序但它却不在SELECT
语句中。
JPQL提供了一种替代方案,用一条语句更新或删除一个或多个实体。JPQL中批处理支持一次可以处理一个实体类型(和它的子类)。也就是说,你只能在FROM
或UPDATE
指定唯一实体。这里是一个UPDATE
查询的语法。
SET state_field | single_value_association_field = value
{,state_field | single_value_association_field = value }*
value
引用必须与你要更新的状态字段或是单值关联字段的类型相同。你可以将下列任意值用于value
。
-
一个算术表达式
-
字符串
-
时间日期(datetime)
-
布尔值(boolean)
-
枚举类型(enum)
-
简单实体表达式
-
NULL
DELETE
语句看起来如下。
[WHERE clause]
WHERE
语句的语法与SELECT
相同(更多信息参见SELECT
语句一节)。DELETE
操作符只影响FROM
语句指定的实体及其子类。此操作不会级联到任何相关的实体。此外,UPDATE
操作符不更新实体的version一列。
批处理操作最终转换成SQL并在数据库中执行,绕过了持久化环境(persistence context)。当使用一个事务作用域(transaction-scoped)的持久化环境,会在它们所在事务内执行或在一个事务开始时执行。
批量操作和扩展的持久化环境(extended persistence context)的组合在管理上有所不同。因为一个扩展持久化环境不会与数据库同步,直到参与到一个事务中去,你可以有一些实体已经删除了,但仍可能存在于持久化环境中。
持久化实现通常会在执行批处理操作前禁用一些缓存功能。基于不同的实现,部分或全部缓存功能会被禁用。频繁的使用批处理操作会影响应用程序的性能。基于这些原因,你应该在它们自己的事务内或是一个事务开始时执行批处理操作。
实例当你使用一个UPDATE
或是DELETE
查询时,你一定会用到Query API提供的方法--executeUpdate()
,来执行更新或是删除操作。如果你使用getResultList()
或getSingleResult()
,持久化实现会抛出一个IllegalStateException
异常。同样,你用executeUpdate()
来执行一个SELECT
查询,持久化实现会抛出一个IllegalStateException
异常。
下面是一些批量更新的例子。
-
一个论坛帖子数量的两倍。
Query q2 = em.createQuery("UPDATE Forum AS f "
+"SET f.forumPostCount = f.forumPostCount * 2");
q2.executeUpdate(); -
将所有的
Query q = em.createQuery("UPDATE Role AS r " +Role
实体的dateUpdated
字段设置为当前日期和时间。
"SET r.dateUpdated = CURRENT_TIMESTAMP");
q.executeUpdate(); -
将布尔字(
// Assume we already fetched the correct User identified bypruningEnabled)
)段值设置为true
。在agoraBB
应用程序中,用EntityListener
类来设置dateUpdated
和updatedByUser
字段。version字段是由持久化实现进行管理。避开这些弯弯角角,当你执行批量操作时,你的查询写成更新字段。还有,要注意的是,executeUpdate
返回实体更新的数目(删除时返回删除的数量)。
// the variable adminUser
Query forumUpdate = em.createQuery("UPDATE Forum AS f " +
"SET f.pruningEnabled = TRUE, f.dateUpdated = CURRENT_TIMESTAMP, " +
"f.version = f.version + 1, f.updatedByUser = :user");
forumUpdate.setParameter("user", adminUser);
int entitiesUpdated = forumUpdate.executeUpdate();你可以将
Query pruningReset = em.createQuery("UPDATE Forum AS f " +pruningEnabled
的值设置为null
,将其进行重位。如下所示。
"SET f.pruningEnabled = NULL");
pruningReset.executeUpdate();这条查询设置了
Query enumUpdate = em.createQuery("UPDATE Forum AS f " +enum
类型的字段值。你必须使用enum
类的长限定词,因为enum
不是实体,持久化实现不法获得Status的信息,但它可以识别com.sourcebeat.jpa.model.Status
。
"SET f.status = com.sourcebeat.jpa.model.Status.LOCKED " +
"WHERE f.type = com.sourcebeat.jpa.model.FTPType.ANNONUCEMENT");
int enumUpdateCount = enumUpdate.executeUpdate();
这里有几个DELETE
操作的例子。
-
删除系统中所有无密码的用户(
Query removeRoles = em.createQuery("DELETE FROM User u " +Users
)。
"WHERE u.password = NULL");
int rolesRemoved = removeRoles.executeUpdate(); -
删除没有主题(
Query removeForums = em.createQuery("DELETE FROM Forum f " +Topics
)的论坛(Forum
)。
"WHERE f.topics IS EMPTY");
int forumsRemoved = removeForums.executeUpdate();
JPQL提供了大量实体查询,分组,排序和总结的功能。包括丰富的关联(join)操作支持和即时读取延迟关联关系(eagerly fetch lazy relationship)的能力。你已经学习所支持的各种函数和表达式,以及SELECT
语句所能提供的各种选项。本章最后探讨了批量处理操作,它具备使用单条查询语句(实际是指UPDATE
或DELETE
语句)影响一个或多个实体的能力。
现在你应该很熟悉了JPQL所能提供的丰富的功能,利用它,从简单到复杂一步步地创建自己的查询。
j2ee技术交流群 QQ:41732384