JPQL(转)

欢迎访问我的网站交流技术 www.ithuzhu.com

这是www.sourcebeat.com上JPA101一书的样章,可以从TheServerSide.com上下载,作者blog是http://www.jroller.com/cmaki/entry/jpa_presentation,翻译问题请留言,修正之后会发布pdf版本。

摘要

本章探讨Java持久化查询语言(JPQL),在简单的向你介绍JPQL后,会直接切入创建查询这个话题。本章会涉及一个查询的方方面面,包括fetch join操作。fetch join操作会提前读取延时关联(lazy relationship),以消除LazyInitializationException异常,这在使用ORM方案时,常常困扰很多应用程序的开发。接下来,会了解一下JPQL的打量操作支持。学习完本章时,你会对JPQL有一个全面的认识。

概述

JPQL是一种与数据库无关的,基于实体(entity-based)的查询语言。它是EJB QL的一个扩展,并加入了很多EJB QL中所没有的新特性。JPQL支持projection(可以查询某个实体的字段而不需要查询整个实体),批量操作(update和delete),子查询,join,group by和having操作。所有的JPQL操作都在静态查询(命名查询,named query)和动态查询中得到支持。另外,JPQL支持多态,当读取Rank会返回SpecialRankPostCountRank实例(instance)(Rank是一个抽象类,JPQL只返回具体类)。

JPQL在语法上与SQL相似:“ select from [where] [group by] [having] [order by] ”。如果你熟悉SQL,那么对大部分的JPQL语法也不会陌生。JPQL同样支持SQL类似的函数功能,如max和min,但是并不能取代SQL。不管如何相似,但两者之间有一个重要的区别,JPQL操作的是“抽象持久化模型(abstract persistence schema)”,而不是数据库定义的物理模型。抽象持久化模型通常是用JPA元数据(metadata)在ORM文件中或注释(annotation)中进行定义(有关注释和ORM的更多信息参见第2章Entities Part I,第3章Entities Part II,第4章Packaging)。抽象持久化模型包括实体,它们的字段及定义的所有关联关系。JPA持久化工具可以将JPQL转换成内置的SQL查询语句。这就意味着你可以在查询语句的SELECT指定一个实体或是它的字段,或者在WHERE语句中使用多个实体。你可以用点号(.)来访问查询语句中的SELECT部分的关联关系。下面这个例子是访问agoraBB对象模型中从PrivateMessageUser的关联。

 

				
					SELECT p.toUser FROM PrivateMessage p
				
			

这个查询会得到PrivateMessage所属的User

虽然可以说JPQL可以将所有的操作当成查询,但实际上只有三种不同的“查询”:SELECT查询(真正可以算得上的一个查询),更新查询(不是查询而是更新语句),或者删除查询(也不是真正的查询而是删除语句)。JPA规范中将所有的这些查询当成查询,你可以用同样的语法定义这三种查询,它们之间的差别会反应到数据库上。

本章包含以下话题:

 

  • JPQL概述

  • Join

  • WhereGroup ByHaving

  • SELECT语句

  • 批量操作支持

  • 实例

JPQL操作的就是抽象持久化模型,即一个持久化单元(persistence unit)中实体所表示的模型。使用SQL,你可以直接查询数据库中表的字段;使用JPQL,你可以查找实体的属性。一个JPQL语句的各个组成部分都是针对实体或者实体的属性进行操作。你根本不会用到一个实体所映射的数据库表和字段。下面是你可以创建紧最基本的JPQL语句:

 

SELECT u FROM User

查询从数据库返回所有的User实例。

注意

除实体名称和实体字段外,JPQL查询不区分大小写。所以在查询的其它部分你可以使用任意形式。本章中,所有JPA保留标志符我们使用大写,在你的应用程序中并不要求要这么做。

类型

JPQL是一种强类型语言,一个JPQL语句中每个表达式都有类型。

注意

强类型语言中规定某种表达式对某种类型是合法的。如果在Java中编译"123"+3,你会得到一个类型不相容的编译器错误。

例如,有下面这样一个查询语句:

 

SELECT u.username FROM User u

在这个查询语句中,u.username一个表达式的结果是一个String类型。这个查询会返回一个String对象的list。list中的各个对象代表系统中一个不同的用户名。JPQL规范将一个实体(属性)中所饮食的各种类型称为抽象模型类型(abstract schema type)。

一个实体可以包含一个或多个下面的抽象模型类型:

  • 状态字段(state-field):这种类型包括一个实体不代表关联关系的任何字段或属性。字段的类型或者一个属性get方法的返回结果决定了状态字段的抽象模型类型。

  • 关联字段(association-field):这种类型包括了一个实体中任何表示关联关系的字段或属性。关联字段的抽象模型类型就是目标实体的抽象模型类型。

JPQL语句的作用范围是由你执行查询语句中持久化单元(persistence unit)的实体决定的。

在一个查询中,实体是通过实体名称来引用的。实体的名称就是你使用@Entity注释时指定的名称,或者是实体的限定词(unqualified name)。假如有这样一个实体。

 

			
package com.sourcebeat.jpa.model;

@Entity(name="message")
public class PrivateMessage extends ModelBase {
	// fields/methods removed for readability
}			
			

实体名称是“ message ”,因为你在使用@Entity时候指定了名称(在一个持久化单元内实体名称必须是唯一的)。在下面的代码中,实体的限定词是User(以一个大写的“U”开头)。实体的全名是com.sourcebeat.jpa.model.User,但它的限定词 是User

 

				
package com.sourcebeat.jpa.model;

@Entity
public class User extends ModelBase {
	// fields/methods removed for readability
}				
				
			

当我们在JPQL中引用实体时,必须使用@Entity注释指定的名称或是实体的限定词 。一些JPA实现(如,Hibernate)允许你使用全名,但有的(如,Toplink)则抛出异常。为了保证移植性,在查询中最好使用限定词 。

一个标识变量(identification variable)是一个在FROM语句中指定的一个标志符号。在下面的查询语句中,u就是一个标识变量。u类型就是能够识别名称User的实体的抽象模型类型。在本例中,实体是User(前面已经定义),所以u的类型是User

 

SELECT u FROM User u				
				

标识变量也可以使用JOIN关键字定义,例如。

 

SELECT r.name FROM User u JOIN u.roles r				
				

这里,标识变量有urr表示任何能够从一个User实例中直接访问的Role

保留标志符

JPA定义了以下保留标志符(虽然下面列表中显示是大写形式,保留标志符并不区分大小写,所以SELECTselectSelECt等同。)

 

SELECT, FROM, WHERE, UPDATE, DELETE, JOIN, OUTER, INNER, LEFT, GROUP, 
BY, HAVING, FETCH, DISTINCT, OBJECT, NULL, TRUE, FALSE, NOT, AND, OR, 
BETWEEN, LIKE, IN, AS, UNKNOWN, EMPTY, MEMBER, OF, IS, AVG, MAX, MIN, 
SUM, COUNT, ORDER, BY, ASC, DESC, MOD, UPPER, LOWER, TRIM, POSITION,
CHARACTER_LENGTH, CHAR_LENGTH, BIT_LENGTH, CURRENT_TIME, CURRENT_DATE, 
CURRENT_TIMESTAMP, NEW, EXISTS, ALL, ANY, SOME.
			

UNKNOWN目前在JPQL中还没有用到。

路径表达式

路径表达式就是一个标识符号紧跟一个访问操作符(.)再紧跟一个状态字段或是关联字段。只要它们不是集合(collections),就可以遍历关系。下图是agoraBB项目对象模型的一部分,用于解释路径表达式。

图 1. 路径表达式模型实例

路径表达式模型实例

PrivateMessage所属于的User的关系是由PrivateMessage的关联关系字段toUser表示的。User有一个关联到Role的集合关联字段roles,还有一个关联到UserIpAddress的字段userIpAddress

由于在在这些关系,你可以进行以下操作。

  • PrivateMessage所属用户User的IP地址UserIpAddresses

    SELECT p.toUser.userIPAddresses from PrivateMessage p
  • PrivateMessage所属用户User的角色Role

    SELECT p.toUser.roles from PrivateMessage p
  • User的角色Role,获取所有的无重复的角色名称。

    SELECT DISTINCT r.name FROM User u JOIN u.roles r
  • User的IP地址UserIpAddresses

    SELECT DISTINCT u.userIPAddresses FROM User u

下图关系略有不同。

图 2. 路径表达式模型实例2

路径表达式模型实例2

图中,论坛Forum与其主题Topics的关系由Forum的字段topics表示,从TopicsPost的关系由Topic的字段topics表示。从对象关系图可以看出哪些JPQL是合法的,哪些不合法。

  • 查询论坛Forum的主题Topic,要获取标题(subject):路径表达是不合法的,不能直接通过集合访问。

    SELECT f.topics.subject FROM Forum f – ILLEGAL
  • 查询论坛Forum主题Topic的帖子Post,使用JOIN操作符,表达式是合法的。

    SELECT t.subject FROM Forum f JOIN f.topics AS t
  • 查询论坛Forum主题Topic的帖子Post,,路径表达是不合法的,不能直接通过集合访问。

    SELECT f.topics.posts FROM Forum f – ILLEGAL
  • 查询论坛Forum主题Topic的帖子Post,使用JOIN操作符,表达式是合法的。

    SELECT p FROM Forum f JOIN f.topics t JOIN t.posts p

注意

虽然JPA规范中明确表示无法访问一个集合关系字段,据我在HibernateToplink上的测试,在一个目标实体中可以访问集合关联字段或状态字段。在JPA的BNF范式针对路径访问指明了从Forum访问Topic,所以读取主题就是非法的。如果你要访问一个集合字段的目标,使用第二个例子如示JOIN语法。

总之,你可以使用导航操作符(.)来遍历实体对象关系图,查询的类型是由SELECT语句决定的。SELECT语句可以包含标识变量和路径表达式。路径表达式可以遍历整个对象关系图,只要你从左到右访问的是单值关联字段,你无法访问一个集合字段或是一个状态关联字段。

界定变量(Range Variables)

界定变量使用与&SQL相似的语法,将实体名绑定到一个标志符上。界定变量声明如下(在一个查询语句的SELECT语句):

 

entityName [AS] identification_variable				
				

你可以通过使用多个界定变量来使用同一实体,下面的例子来自JPA规范。

 

SELECT DISTINCT o1
FROM Order o1, Order o2
WHERE o1.quantity > o2.quantity AND
o2.customer.lastname = ‘Smith’ AND
o2.customer.firstname= ‘John’				
				

此查询返回大于John Smith的定单的所有的订单。

 

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允许你关联那些没有显式关联关系或者关联到没有关系但是等同的信息的实体。

INNER JOIN

JPQL中inner join也是用于关系联合。其语法如下:

 

[INNER] JOIN join_association_path_expression [AS] identification_variable				
				

INNERAS都是可选的。虽然它们对查询没有什么影响,你还是可以使用它们来更加清晰的表达你的意图。

join_assocation_path_expression的意思是你可以访问一个关联实体,不管是单值关联还是一个集合。下图演示了两个inner join查询:

图 3. 


因为你不能在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联合查询的回复的标题。

LEFT OUTER JOIN

一个outer join会返回一个实体的所有实例和其它与join criteria匹配的其它实体的实例。一个left join的语法如下:

 

LEFT [OUTER] JOIN join_association_path_expression [AS] identification_variable					
				

[OUTER]是可选的,因为在JPQLLEFT JOINLEFT OUTER JOIN以认为是等同的。使用上图中所示的Forum/Post实体关系,下面的left join操作可以读取所有的Forum和任何与Forum关联的Topic。如果找不到TopicObject数组的第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

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

 

图 4.  Post/User对象关系图

Post/User对象关系图

现在假设你想知道创建的agoraBB系统中每个用户的发帖数量。从上图中可以看,没有从UserPost的关联关系。因为UserPost继承ModelBase MappedSuperclass的,我们知道每个Post对象有一个createdByUserupdatedByUser字段。

使用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

WHERE , GROUP BY , HAVING

这条查询语句会返回一个User实例和他所创建帖子的数量。为了进一步缩小结果范围,你可以使用HAVING过滤分组信息(由GROUP BY标志)。下面的查询会返回User实例及他所发的帖子数量,前提是用户的密码是8个以上的字符。

 

SELECT u, count(p) From Post p JOIN p.createdByUser u GROUP BY u 
HAVING length(u.password) > 8			
			

条件表达式(CONDITIONAL EXPRESSIONS)

条件表达式可以用在一个JPQL查询的WHEREHAVING语句中。你必须了解使用条件表达式时的一些约束。

 

  • 条件表达式中包括LOB状态字段可能无法在数据库之间进行移植。

  • 字符串使用单引号字符串包裹,如'this'。如果要在查询中使用单引号,就要两个一起使用。你无法在查询中使用Java转义方法(例如,/'代表单引号)。布尔(Boolean)字符用TRUEFALSE表示(不区分大小写),数字字符遵从Java规范,不支持日期字符串。同样可以支持Enum,但是你必须使用Enum,全路径名称,如,com.sourcebeat.jpa.model.FTPType

  • 标识变量必须出现在一个SELECTDELETE查询的FROM语句中。如果使用UPDATE,那么标识变量就必须在UPDATE语句中。标识变量往往表示他们所定义的实体类型,而不能表示一个集合中的实体。

  • 你可以使用位置或者命名形式的输入参数,但不能在某一查询混合使用这两种形式。输入参数可以出现一个查询的WHERE语句和(或者)HAVING语句中。

     

    • 位置形式参数的格式是以一个问号(?)打头紧跟一个以1开始的正整数。例如,?1。你可以在一个查询中多个使用同一位置参数,如下所示。

       

      SELECT u FROM User u WHERE u.dateCreated = ?1 OR u.dateUpdated = ?1										
      										
    • 命名形式的参数用一个冒号(:)加一个Java标志符如Java变量名来表示。命名参数表示如下:

       

      SELECT u FROM User u WHERE u.dateCreated = :aDate OR u.dateUpdated = :aDate										
      										

函数与表达式(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 ?2					
					
IN

你可以利用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 NULL					
					
IS 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 EMPTY					
					
MEMBER

可以用[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 f				
				
COUNT

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

SELECT COUNT(f) FROM Forum f				
				
MAX

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

SELECT MAX(f.forumPostCount) FROM Forum f				
				
MIN

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

SELECT MIN(f.dateCreated) FROM Forum f				
				
SUM

计算查询结果作为数值型参数的总和,当参数是浮点类型时返回一个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所能提供的丰富的功能,利用它,从简单到复杂一步步地创建自己的查询。

 

欢迎访问我的网站交流技术 www.ithuzhu.com

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值