分享

JPA查询语言 2

 killerwang249 2013-07-13

JPA查询语言 2

函数与表达式(FUNCTIONS AND EXPRESSIONS)

JPQL支持函数功能,多种的INLIKEBETWEEN样式表达式,及面向集合(collection)的条件表达式。这一节详细讨论写查询可用的各种选择。

一个查询语句中操作符的优先级为:

       
  •    

    导航操作符(.)

           
  •    

    一元符号(+,-)

           
  •    

    乘(*),除(/)

           
  •    

    加(+),减(-)

           
  •    

    比较操作符,=, >, >=, <, <=,<> (不等), [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF]

           
  •    

    逻辑操作符,NOT, AND, OR

       
BETWEEN

你可以用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 NULL

IS 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查询的WHEREHAVING语句中支持字符串函数作为函数表达式。

       
  •    

    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]):返回string2string1的位置。定位函数有一个可选的起始位置start

       
数学函数(ARITHMETIC FUNCTIONS)

JPQL查询的WHEREHAVING语句中支持数学函数作为函数表达式。

       
  •    

    ABS(arithmetic expression):返回算术表达式的绝对值。

           
  •    

    SQRT(arithmetic expression):求算术表达式的方根,返回一个Double。

           
  •    

    MOD(arithmetic expression 1, arithmetic expression 2):求参数1与参数2的模,返回一个整数。

           
  •    

    SIZE(collection-valued path-expression):计算一个集合中元素的数量,并返回一个整数。如果集合为空,返回0。

       
时间函数(DATETIME FUNCTIONS)

支持以下时间函数。

       
  •    

    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)是主题的内容。

构造器函数(CONSTRUCTOR EXPRESSION)

你可以创建一个新的对象作为查询结果。这个对象不要求是实体,但要有一个构造器,它的顺序与类型与SELECT语句一致。下面是一个封装用户统计数据的临时对象。

public class UserStatistics {
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中。

Query q = em.createQuery("SELECT NEW com.sourcebeat.jpa.model.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语句中可以使用以下统计函数(在一个路径表达式上应用)。

AVG

计算查询结果返回的数值型参数的平均值,并返回一个Double整型。

SELECT AVG(f.forumPostCount) FROM Forum fCOUNT

计算所找到的实体的总和,并返回一个Long整型。如果没有找到实体,COUNT返回0。

SELECT COUNT(f) FROM Forum fMAX

计算查询结果作为参数的最大值,返回类型与参数类型一致。MAX函数可以应用到任何可排序的状态字段上,包括数字类型,字符串,字符类型,或日期。

SELECT MAX(f.forumPostCount) FROM Forum fMIN

计算查询结果作为参数的最小值,返回类型与参数类型一致。MIN函数可以应用到任何可排序的状态字段上,包括数字类型,字符串,字符类型,或日期。

SELECT MIN(f.dateCreated) FROM Forum fSUM

计算查询结果作为数值型参数的总和,当参数是浮点类型时返回一个Double类型,当使用BigInteger返回一个BigInteger,当参数为BigDecimal时返回BigDecimal

SELECT SUM(f.forumPostCount) FROM Forum f使用法则(RULES OF USAGE)

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操作符的所在位置。

select from [where] [group by] [having] [order by]

Order By的语法为:

ORDER BY expression [ASC | DESC] {, expression [ASC | DESC]}*

下面是一些合法的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语句中。

SELECT u.username, u.id FROM User u ORDER BY u.password批量处理(BULK OPERATIONS)

JPQL提供了一种替代方案,用一条语句更新或删除一个或多个实体。JPQL中批处理支持一次可以处理一个实体类型(和它的子类)。也就是说,你只能在FROMUPDATE指定唯一实体。这里是一个UPDATE查询的语法。

UPDATE abstract_persistence_scheama_name [AS] identification_variable
SET state_field | single_value_association_field = value
{,state_field | single_value_association_field = value }*

value引用必须与你要更新的状态字段或是单值关联字段的类型相同。你可以将下列任意值用于value

       
  •    

    一个算术表达式

           
  •    

    字符串

           
  •    

    时间日期(datetime)

           
  •    

    布尔值(boolean)

           
  •    

    枚举类型(enum)

           
  •    

    简单实体表达式

           
  •    

    NULL

       

DELETE语句看起来如下。

DELETE FROM abstract_persistence_scheama_name [[AS] identification_variable]
[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();        
  •    

    将所有的Role实体的dateUpdated字段设置为当前日期和时间。

        Query q = em.createQuery("UPDATE Role AS r " +
    "SET r.dateUpdated = CURRENT_TIMESTAMP");

    q.executeUpdate();        
  •    

    将布尔字(pruningEnabled))段值设置为true。在agoraBB应用程序中,用EntityListener类来设置dateUpdatedupdatedByUser字段。version字段是由持久化实现进行管理。避开这些弯弯角角,当你执行批量操作时,你的查询写成更新字段。还有,要注意的是,executeUpdate返回实体更新的数目(删除时返回删除的数量)。

        // Assume we already fetched the correct User identified by
    // 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();    

    你可以将pruningEnabled的值设置为null,将其进行重位。如下所示。

        Query pruningReset = em.createQuery("UPDATE Forum AS f " +
    "SET f.pruningEnabled = NULL");

    pruningReset.executeUpdate();    

    这条查询设置了enum类型的字段值。你必须使用enum类的长限定词,因为enum不是实体,持久化实现不法获得Status的信息,但它可以识别com.sourcebeat.jpa.model.Status

        Query enumUpdate = em.createQuery("UPDATE Forum AS f " +
    "SET f.status = com.sourcebeat.jpa.model.Status.LOCKED " +
    "WHERE f.type = com.sourcebeat.jpa.model.FTPType.ANNONUCEMENT");

    int enumUpdateCount = enumUpdate.executeUpdate();    

这里有几个DELETE操作的例子。

       
  •    

    删除系统中所有无密码的用户(Users)。

        Query removeRoles = em.createQuery("DELETE FROM User u " +
    "WHERE u.password = NULL");

    int rolesRemoved = removeRoles.executeUpdate();        
  •    

    删除没有主题(Topics)的论坛(Forum)。

        Query removeForums = em.createQuery("DELETE FROM Forum f " +
    "WHERE f.topics IS EMPTY");

    int forumsRemoved = removeForums.executeUpdate();    
总结(SUMMARY)

JPQL提供了大量实体查询,分组,排序和总结的功能。包括丰富的关联(join)操作支持和即时读取延迟关联关系(eagerly fetch lazy relationship)的能力。你已经学习所支持的各种函数和表达式,以及SELECT语句所能提供的各种选项。本章最后探讨了批量处理操作,它具备使用单条查询语句(实际是指UPDATEDELETE语句)影响一个或多个实体的能力。

现在你应该很熟悉了JPQL所能提供的丰富的功能,利用它,从简单到复杂一步步地创建自己的查询。

j2ee技术交流群 QQ:41732384

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多