JPA开发文档
1.2 关系对象映射(Object Relational Mapping,ORM)
1.3 Java 数据对象(Java Data Object,JDO)
3.2 主键和实体标识(Primary Key and Entity Identity)
1. 发展中的持久化技术
1.1 JDBC
很多企业应用的开发者选择使用 JDBC 管理关系型数据库中的数据。JDBC支持处理大量的数据,能够保证数据的一致性,支持信息的并发访问,提供 SQL 查询语言查找数据。JDBC 所使用的关系模型不是为保存对象而设计的,因此迫使开发者选择在处理持久数据时放弃面向对象编程,或者自己去开发将面向对象特性(比如:类之间的继承)和关系型数据库进行映射的专有解决方案。
1.2 关系对象映射(Object Relational Mapping,ORM)
ORM 是目前完成对象和关系数据表之间的映射最好的一种技术, 这些 ORM 框架处理对象和关系数据库之间的协调工作,将开发者从这部分工作中解脱出来,集中精力处理对象模型。阻碍 ORM 发展的问题是,现有的每一种 ORM 产品都有自己特有的 API,开发者只能将自己的代码绑定到某一个框架提供商的接口上,这种状况形成了厂商锁定,意味着一旦该框架提供商无法解决系统中出现的严重错误,或者因为其它的原因转而采用其它的框架,将会给开发者的企业应用带来极大的困难,唯一的解决办法是重写所有的持久化代码。
1.3 Java 数据对象(Java Data Object,JDO)
JDO 是 Java EE 标准中另外一个支持管理持久化数据的规范,JDO 规范使用和 JPA 非常类似的 API,只是通常是通过 JCA 技术集成到应用服务器上。但是 JDO 是针对轻量级容器而设计的,不能够支持容器级别的声明式安全、事务特性,也无法对远程方法调用提供支持。
1.4 Java Persistence API(JPA)
EJB 3.0 规范由三部分组成:EJB3.0 Simplified API、EJB 核心规范(EJB Core Contracts and Requirements)和 JPA(Java Persistence API)。JPA 规范部分详细的介绍了 JPA 中实体 Bean 的定义,并介绍了实体 Bean 支持的注释、全新的查询语言、实体管理接口、容器实现规范等内容。
JPA 标准制定过程中充分吸收了目前已经出现的所有持久化技术的所有优点,摒弃了它们存在的局限,使 JPA 在简单易用、查询能力等方面表现突出。
JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问 API,这保证了基于 JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
JPA 的主要目标之一就是提供更加简单的编程模型:在 JPA 框架下创建实体和创建 Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity 进行注释;JPA 的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA 基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
可媲美 JDBC 的查询能力
JPA 定义了独特的 JPQL(Java Persistence Query Language),JPQL 是 EJB QL 的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
JPA 中能够支持面向对象的高级特性,比如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
支持内容: | JDBC | ORM | JDO | EJB 3(JPA) |
Java 对象 | No | Yes | Yes | Yes |
高级OO原理 | No | Yes | Yes | Yes |
事务完整性 | Yes | Yes | Yes | Yes |
并发 | Yes | Yes | Yes | Yes |
大数据集 | Yes | Yes | Yes | Yes |
现有 Schema | Yes | Yes | Yes | Yes |
关系型和非关系型数据存储 | No | No | Yes | No |
查询 | Yes | Yes | Yes | Yes |
严格的标准/可移植 | No | No | Yes | Yes |
简单易用 | Yes | Yes | Yes | Yes |
表 1 持久化技术的优缺点
2. JPA 体系架构
JPA 中定义一套类和接口用于实现持久化管理和对象/关系的映射,下面这张图中显示了 JPA 的主要组件以及它们之间的相互关系。
图1 JPA 主要组件和相互关系
- EntityManagerFactory
EntityManagerFactory 是 EntityManager 的工厂类,负责创建 EntityManager 对象。
- EntityManager
EntityManager 是 JPA 应用中使用的基本对象,通过它提供的相应方法可以管理持久化对象,也可以新建或者删除持久化对象。EntityManager 还负责创建 Query 实例。在容器外使用时,EntityManagerFactory 和 EntityManager 之间是一对一的关系。
- Entity
EntityTransaction 提供 Entity 操作时需要的事务管理,和 EntityManager 是一对一的关系。在查询操作时不需要使用 EntityTransaction,而在对象持久化、状态更新、对象删除等情况下则必须使用显式的使用 EntityTransaction 的相关方法管理事务。
- Query
Query 是查询实体的接口,Query 对象可以从 EntityManager 中获得。根据 EJB 3.0 规范中的描述,Query 接口需要同时支持 JPQL 和原生态 SQL 两种语法。
- Persistence
Persistence 是一个工具类,负责根据配置文件提供的参数创建 EntityManagerFactory 对象。
下面的代码演示了如何通过 JPA 提供的接口和 JPQL 查询语言完成实体查询和更新的例子,例子中的代码假定运行在非 Java EE 环境中。
清单 1 在非 Java EE 环境使用 JPA 接口的例子
EntityManagerFactory factory = Persistence.createEntityManagerFactory (“mysql”);
// 从 EntityManagerFactory 实例 factory 中获取 EntityManager
EntityManager em = factory.createEntityManager(PersistenceContextType.EXTENDED);
// 实体的更新需要在事务中运行
EntityTransaction tx = em.getTransaction ();
tx.begin ();
// 查找所有公司中的女性雇员
Query query = em.createQuery ("select e from Employee e where e.sex = 'femail'");
List results = query.getResultList ();
// 给所有女性雇员增加半天假期
for (Object res : results)
{
Employee emp = (Employee) res;
emp.setHoliday (emp.getHoliday () +0.5);
}
// 提交事务(持久化所有更新)
tx.commit ();
em.close ();
factory.close ();
下面的代码显示了在 EJB 容器中开发 JPA 应用时的接口使用情况,由于容器中的 EntityManager 是注入的,事务也是声明式的,因此在容器中完成上面的业务逻辑要简单得多。
清单 2 在容器中运行的 JPA 例子
* 在容器中运行 JPA 应用时,EntityManager 接口的实例”em”
* 是通过 @Resource 注释注入的。事务也通常是声明式的。
*/
// 查找所有公司中的女性雇员
Query query = em.createQuery ("select e from Employee e where e.sex = 'femail'");
List results = query.getResultList ();
// 给所有女性雇员增加半天假期
for (Object res : results)
{
Employee emp = (Employee) res;
emp.setHoliday (emp.getHoliday () +0.5);
}
3. Entity Bean
EJB3 Entity可以是很简单的java bean,只要批注了@Entity或者在xml配置中作了说明,就被做一个可持久化的Entity处理。 但还是需要遵行一定的规则:
· Entity类必须要有一个无参数的public或者protected的Constructor。
· 如果在应用中需要将该Entity类分离出来在分布式环境中作为参数传递,该Entity Class需要实现java.io.Serialzable接口。
· Entity类不可以是final,也不可有final的方法。
· abstract类和Concrete实体类都可以作为Entity类。
· Entity类中的属性变量不可以是public。Entity类的属性必须通过getter/setter或者其他的商业方法获得。
3.1定义对Entity中属性变量的访问
在绝大部分的商业应用,开发人员都可以忽略这部分无需关心。但如果你需要编写复杂的Entity类的话,你需要了解这个部分。复杂的Entity类是指在Entity类的getter/setter和商业方法中包含比较复杂的业务逻辑而不是仅仅返回/符值某个属性。
在大部分的情况下,我们都建议使Entity类中setter/getter中的逻辑尽可能简单,除了必要的校验符值外,不要包含复杂的业务逻辑,例如对关联的其他Entity类进行操作。但有些情况下,我们还是需要在Entity类的setter/getter方法中包含商业逻辑。这时候,采用何种属性访问方式就可能会影响代码的性能甚至是逻辑正确产生影响。
EJB3持久化规范中,在默认情况下所有的属性都会自动的被持久化,除非属性变量用@Transient元数据进行了标注。针对可持久化属性定义了两种属性访问方式(access): FIELD和PROPERTY。
· 如果采用access=FIELD, EJB3 Persistence运行环境直接访问对象的属性变量,而不是通过getter。这种访问方式也不要求每个属性必须有getter/setter。如果需要在getter中包含商业逻辑,应该采用access=FIELD的方式。
· 如果采用access=PROPERTY, EJB3 Persistence运行环境将通过Entity类上的getter来访问对象的属性变量,这就要求每个属性变量要有getter/setter方法。在EJB3中,默认的属性访问方式是PROPERTY。access=PROPERTY时getter/setter的逻辑应该尽量简单。
规范中access方式还有多一层含义。就是采用access=FIELD时,元数据应该批注在属性上。
@Id(generate=GeneratorType.NONE)
private int id;
private String foo;
/**
* The entity class must have a no-arg constructor.
*/
public HelloEntityBean() {
}
public int getId() {
return id;
}
采用access=PROPERTY(默认方式)时,元数据应该批注在对应属性变量的getter上。
private int id;
private String foo;
/**
* The entity class must have a no-arg constructor.
*/
public HelloEntityBean() {
}
@Id(generate=GeneratorType.NONE)
public int getId() {
return id;
}
Entity类中的属性变量可以是以下数据类型:
- 原始数据类型和他们的对象类型
- java.lang.String
- java.math.BigInteger
- java.math.BigDecimal
- java.util.Date
- java.util.Calendar
- java.sql.Date
- java.sql.Time
- java.sql.Timestamp
- byte[]
- Byte[]
- char[]
- Character[]
- enums
- Entity类
- 嵌入实体类(embeddable classes)
还可以是以下集合类型:
- java.util.Collection和它的实体类
- java.util.Set和它的实体类
- java.util.List和它的实体类
- java.util.Map和它的实体类
3.2 主键和实体标识(Primary Key and Entity Identity)
每个Entity类都必须有一个主键。在EJB3中定义了两种主键:键单主键和复合主键。
简单主键必须对应Entity中的一个属性变量(Instance Variable),而该属性对应数据库表中的一列。使用简单主键,我们只需要用@Id元数据对一个属性变量或者她的getter方法进行批注。
当我们需要使用一个或多个属性变量(表中的一列或多列)联合起来作为主键,我们需要使用复合主键。复合主键要求我们编写一个复合主键类( Composite Primary Key Class )。复合主键类需要符合以下一些要求:
· 复合主键类必须是public和具备一个没有参数的constructor
- 复合主键类的每个属性变量必须有getter/setter,如果没有,每个属性变量则必须是public或者protected
- 复合主键类必须实现java.io.serializable
- 复合主键类必须实现equals()和hashcode()方法
- 复合主键类中的主键属性变量的名字必须和对应的Entity中主键属性变量的名字相同
- 一旦主键值设定后,不要修改主键属性变量的值
复合主键的例子。Entity类Person,它的主键属性变量是firstName和lastName。
@Id
private String firstName;
@Id
private String lastName;
public Person() {
}
Person的复合主键类:
public class PersonPK implements java.io.Serializable{
private String firstName;
private String lastName;
public PersonPK() {
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
4. EntityManager
对Entity进行操作的API都设计在javax.persistence.EntityManager接口上。EntityManager,顾名思义是管理所有EJB 3运行环境中的所有Entity。 EntityManager根据运行的环境不同分为容器管理的EntityManager和应用管理的EntityManager。
4.1 配置和获得EntityManager
在J2SE环境中,EJB3定义了一个javax.persistence.Persistence类用于启动EJB3运行环境。要获得EntityManager,首先需要通过javax.persistence.Persistence获得EntityManagerFactory,然后调用EntityManagerFactory.createEntityManager()方法获得。
// 获得默认当前的EntityManagerFactory
final EntityManagerFactory emf = Persistence.createEntityManagerFactory();
final EntityManager entityManager = emf.createEntityManager();
当调用Persistence.createEntityManagerFactory()的时候,Persistence会做以下的步骤:
- 搜索当前jar包的META-INFO/persistence.xml配置文件
- 如果没有在META-INFO下找到persistence.xml,搜索当前线程的ContextClassLoader中的persistence.xml
- 根据获得的persistence.xml初始化EntityManagerFactory
4.2 Entity的生命周期和状态
在EJB3中定义了四种Entity的状态:
- 新实体(new)。Entity由应用产生,和EJB3 Persistence运行环境没有联系,也没有唯一的标示符(Identity)。
- 持久化实体(managed)。新实体和EJB3 Persistence运行环境产生关联(通过persist(), merge()等方法),在EJB3 Persistence运行环境中存在和被管理,标志是在EJB3 Persistence运行环境中有一个唯一的标示(Identity)。
- 分离的实体(detached)。Entity有唯一标示符,但它的标示符不被EJB3 Persistence运行环境管理, 同样的该Entity也不被EJB3 Persistence运行环境管理。
- 删除的实体(removed)。Entity被remove()方法删除,对应的纪录将会在当前事务提交的时候从数据库中删除。
- 图2 状态的转化
4.3 持久化Entity(Persist)
final EntityManagerFactory emf = Persistence.createEntityManagerFactory();
final EntityManager entityManager = emf.createEntityManager();
final HelloEntityBean hello = new HelloEntityBean( 1, "foo" );
EntityTransaction trans = entityManager.getTransaction();
trans.begin();
// 持久化hello,在此操作之前hello的状态为new
entityManager.persist( hello );
// 这时hello的状态变为managed
trans.commit();
entityManager.close();
// 这时hellow的状态变为detached.
当保存一个Entity时,以该对象为根对象的整个对象图都会自动的被保存。但在EJB3中,我们仍然可以通过关系元数据(比如OneToOne,OneToMany)的cascade属性来精确定义保存的级联行为。 下面我们来看看不同的cascade属性的区别。
不配置cascade的情况下,EJB3 Persistence运行环境默认不会采用Persistence by reachability。
public class Father{
@Id
int id
String name;
// OneToOne没有配置cascade属性,因此默认不会使用Persistence by reachablity
@OneToOne
Son mySon
public Father( int id, String name, Son mySon ){
this.id = id;
this.name = name;
this.mySon = mySon;
}
}
现在来保存一个Father和Son。
final EntityManager manager = emf.createEntityManager();
manager.getTransaction().begin;
Son mySon = new Son();
Father = new Father( 1, "father" mySon );
// 保存Father
manager.persist( father );
// 由于OneToOne关系中没有配置casacade属性,father 关联的mySon不会被自动保存,需要分别保存
manager.persist( mySon );
manager.getTransaction().commit();
manager.close();
现在我们配置casacde=CascadeType.ALL
public class Father{
@Id
int id
String name;
// OneToOne配置cascade=CascadeType.ALL,配置cascade=CascadeType.PERSIT也对persist操作也可以获得同样的效果。
// CascadeType.ALL包含CascadeType.PERSIST。
@OneToOne(cascade=CascadeType.ALL)
Son mySon
public Father( int id, String name, Son mySon ){
this.id = id;
this.mySon = mySon;
this.name = name;
}
}
在代码中同样持久化Father和mySon。
final EntityManager manager = emf.createEntityManager();
manager.getTransaction().begin;
Son mySon = new Son();
Father = new Father( 1, mySon );
// 保存Father。由于OneToOne关系中配置casacade=CascadeType.ALL属性,关联的mySon会自动地被持久化
manager.persist( father );
manager.getTransaction().commit();
manager.close();
建议在应用中尽可能使用cascade=CascadeType.ALL来减少持久化操作的复杂性和代码量,特别是在有复杂对象关系图的时候。
4.4 获取Entity
如果知道Entity的唯一标示符,我们可以用find()方法来获得Entity。
Father father = manager.find( Father.class, new Integer( 1 ) );
// 由于JDK1.5支持自动转型,也可以如下使用
Father father = manager.find( Father.class, 1 );
/*
* 或者,可以用Entity名字作为查找。但无法利用JDK 1.5的自动转型功能,
* 需要使用对象作为查找主键,并需要对获得Entity进行转型
*/
Father father = (Father)manager.find( "com.redsoft.samples.Father", new Integer( 1 ) );
4.5 更新Entity
对Entity的更新必须在事物内完成。和persist中一样,关系元数据的cascade属性对是否集联删除有影响。
transaction.begin();
Father father = manager.find( Father.class, 1 );
// 更新原始数据类型
father.setName( "newName" );
// 更新对象引用
Son newSon = new Son();
father.setSon( newSon );
// 提交事务,刚才的更新同步到数据库
transaction.commit();
4.6 删除Entity
对Entity的删除必须在事物内完成。
transaction.begin();
Father father = manager.find( Father.class, 1 );
// 如果father/son的@OneToOne的cascade=CascadeType.ALL,在删除father时候,也会把son删除。
// 把cascade属性设为cascade=CascadeType.REMOVE有同样的效果。
manager.remove( father );
// 提交事务,刚才的更新同步到数据库
transaction.commit();
4.7 脱离/附合(Detach/Merge)
在三层或者分布式应用中,我们很多时候需要Entity能脱离EntityManager,避免长时间保持EntityManager打开占用资源和可以在不同的JVM之间传递Entity。
在脱离EJB3 Persistence Runtime(EntityManager)的管理后,我们仍然可以读取或者修改Entity中的内容。而在稍后的时间,我们又可以将Entity重新和原有或者新的EntityManager附合,如果附合前Entity被改动过,更改的数据可以自动的被发现并和数据库同步。
EntityManager entityManager = emf.createEntityManager();
// 这时Father还是被EntityManager管理的
Father father = manager.find( Father.class, 1 );
// 当entityManger关闭的时候,当前被entityManager管理的Entity都会自动的脱离EntityManager,状态转变为detached
entityManager.close();
// 脱离EntityManager后,我们仍然可以修改Father的属性
father.setName( "newName" );
// 在稍后的,我们可以将father重新附和到一个新的或者原来的EntityManager中
EntityManager newEntityManager = emf.createEntityManager();
// 附合( merge )需要在事务中进行
newEntityManager.getTransaction().begin();
newEntityManager.merge( father );
// commit后father中的被修改的内容会同步到数据库。
newEntityManager.getTransaction().commit();
5. JPA Query
JPA的查询语言(JP)是一种和SQL非常类似的中间性和对象化查询语言。它可以被编译成不同的底层数据库能接受的SQL,从而屏蔽不同数据库的差异,确保用JPQL查询语言编写的代码可在不同的数据库上运行。比起EJB 2.1的查询语言,EJB3可以运行期构造,支持多态,远远比EJB 2.1的查询更灵活和功能强大。在程序中使用JPQL可以使用大写(SELECT)或者小写(select),但不要大小写(比如:Select)混合使用。
5.1 Query接口
javax.persistence.Query是EJB3查询操作的接口。进行查询,首先要通过EntityManager 获得Query对象。
public Query createQuery(String ejbqlString);
下面我们做一个最简单的查询,查询所有的com.redsoft.samples.Order类。
final Query query = entityManager.createQuery( "select o from Order o");
final List result = query.getResultList();
final Iterator iterator = result.iterator();
while( iterator.hasNext() ){
// 处理Order
}
注意"from Order"。"Order"在EJB3查询中称为com.redsoft.samples.Order类的abstract schema Type。查询Entity在JPQL中都是针对Entity的Abstract Schema Type进行查询。 在同一个EntityManagerFactory中,不允许同时有两个Abstract Schema Type相同的Entity类。比如不允许同时有com.redsoft.samples.Order和com.redsoft.foo.Order。
Query返回一个List的集合结果,我们可以用Iterator或者List.get( int )的方法来获得每个符合条件的Entity。
如果查询结果结合中包含所有符合条件的Entity, EJB3 Persistence运行环境默认会自动缓存每次查询的结果。这样下次同样的查询操作就无需访问数据库,而直接从缓存中返回结果集合。但如果在下次查询操作之前,有针对被缓存的Entity类进行update/insert/delete操作,则缓存的结果集合会自动被清空,这样下次查询就会从数据库获得数据, 确保查询总是获得正确的结果,避免缓存脏数据。
有时候查询会返回海量的数据。注意关闭对集合结果的缓存。
// 假设返回的结果数量巨大
final Query query = entityManager.createQuery( "select o from Order o");
// 关闭对查询结果的缓存
query.setHint( Constants.QUERY_RESULT_CACHE, "false");
final List result = query.getResultList();
final Iterator iterator = result.iterator();
// 这里我们可以处理海量的数据
while( iterator.hasNext() ){
// 处理Order
}
5.2 简单查询
下面是一个简单查询的例子,可以看到和SQL的使用方法很类似。
final Query query = entityManager.createQuery( "select o from Order o where o.id = 1");
final Query query = entityManager.createQuery( "select o from Order o where o.id = 1 and o.confirm = 'true' ");
final Query query = entityManager.createQuery( "select o from Order o where o.id = 1 or o.customer = 'foo' ");
// address是Order类上的一个对象变量属性,Address有一个streetNumber的属性
final Query query = entityManager.createQuery( "select o from Order o where o.address.streetNumber >= 123" );
注意条件语句中查询的是Entity的属性,属性的名字需要和Entity中的属性变量名字一致。
5.3 使用参数查询
参数查询也和SQL中的参数查询类似。JPQL支持两种方式的参数定义方式: 命名参数和位置参数。在同一个查询中只允许使用一种参数定义方式。
final Query query = entityManager.createQuery( "select o from Order o where o.id = :myId");
// 设置查询中的参数
query.setParameter( "myId", 2 );
// 可以使用多个参数
final Query query = entityManager.createQuery( "select o from Order o where o.id = :myId and o.customer = :customerName" );
// 设置查询中的参数
query.setParameter( "myId", 2 );
query.setParameter( "customerName", "foo" );
final Query query = entityManager.createQuery( "select o from Order o where o.id = ?1");
// 设置查询中的参数
query.setParameter( 1, 2 );// 1表示第一个参数,2是参数的值
//或者
final Query query = entityManager.createQuery( "select o from Order o where o.id = ?1").setParameter( 1, 2 );
// 可以使用多个参数
final Query query = entityManager.createQuery( "select o from Order o where o.id = ?1 and o.customer = ?2" );
// 设置查询中的参数
query.setParameter( 1, 2 );
query.setParameter( 2, "foo" );
如果在未来需要在不同的EJB3 运行环境中运行,请使用位置参数,保证应用是可移植的。
5.4 排序(order by)
下面是一个简单查询的例子,可以看到和SQL的使用方法很类似。"ASC"和"DESC"分别为升序和降序,如果不显式注明,JPQL中默认为asc升序。
// 不注明的话,默认为asc为升序,
final Query query = entityManager.createQuery( "select o from Order o order by o.id");
final Query query = entityManager.createQuery( "select o from Order o order by o.address.streetNumber desc");// desc为降序
final Query query = entityManager.createQuery( "select o from Order o order by o.id, o.address.streetNumber");
5.5 查询部分属性
在前面的例子中,都是对针对Entity类的查询,返回的也是被查询的Entity类的实体。JPQL也允许我们直接查询返回我们需要的属性,而不是返回整个Entity。在一些Entity中属性特别多的情况,这样的查询可以提高性能。
// 直接查询我们感兴趣的属性(列)
final Query query = entityManager.createQuery( "select o.id, o.customerName, o.address.streetNumber from Order o order by o.id");
// 集合中的不再是Order,而是一个Object[]对象数组
final List result = query.getResultList();
// 第一个行
Object[] row = result.get( 0 );
// 数组中的第一个值是id
int id = Integer.parseInt( row[0].toString() );
String customerName = row[1].toString();
String streetNumber = Integer.parseInt( row[2].toString() );
5.6 查询中使用构造器(Constructor)
JPQL支持将查询的属性结果直接作为一个java class的构造器参数,并产生实体作为结果返回。
// 我们把需要的三个属性作为一个class( OrderHolder )的构造器参数,并使用new函数。
Query query = entityManager.createQuery("select new com.redsoft.ejb3.dummy.OrderHolder ( o.id, o.vender, o.partNumber ) FROM Order AS o");
// 集合中的结果是OrderHolder
List result = query.getResultList();
该java class不需要是Entity Class。new要求java class使用全名。
5.7 聚合查询(Aggregation)
象大部分的SQL一样,JPQL也支持查询中的聚合函数。目前EJB QL支持的聚合函数包括:
- AVG
- SUM
- COUNT
- MAX
- MIN
final Query query = entityManager.createQuery( "select MAX( o.id ) from Order where o.customerName='foo'");
// 如果我们知道结果是单个,我们可以用getSingleResult()获得结果
final Object result = query.getSingleResult();
// 由于Order中id的类型为long,
final Long max = (Long)result;
// 在一些数据库中max函数返回的结果的类型不一定于id对应的列的类型相符,更安全的方式可以采用string来转型
fina long max = Long.parseLong( result.toString() );
聚合函数也可以作为被查询的一个属性返回。
// 返回所有的订单的生产厂商和他们的订单价值总额
final Query query
= entityManager.createQuery( "select o.vender, sum(o.amount) FROM Order o group by o.vender");");
和SQL一样,如果聚合函数不是select...from的唯一一个返回列,需要使用"GROUP BY"语句。"GROUP BY"应该包含select语句中除了聚合函数外的所有属性。
// 返回所有的订单的生产厂商的的名字,货物号码和每种货物的订单价值总额
// 注意group by后面必须包含o.vender和o.partNumber
final Query query
= entityManager.createQuery( "select o.vender, o.partNumber, sum(o.amount) FROM Order o group by o.vender,o.partNumber");
如果还需要加上查询条件,需要使用"HAVING"条件语句而不是"WHERE"语句。
// 返回所有的订单的生产厂商是"foo"的货物号码和每种货物的订单价值总额
// 这里"having o.vender = 'foo'为条件
final Query query
= entityManager.createQuery( "select o.vender, o.partNumber, sum(o.amount) FROM Order o
group by o.vender,o.partNumber having o.vender='foo'");
在"HAVING"语句里可以跟"WHERE"语句一样使用参数。
// 返回所有的订单的生产厂商是"foo"的货物号码和每种货物的订单价值总额
// 这里"having o.vender = 'foo'为条件
final Query query
= entityManager.createQuery( "select o.vender, o.partNumber, sum(o.amount) FROM Order o
group by o.vender,o.partNumber having o.vender=?1");
query.setParameter( 1, "foo" );
final List result = query.getResultList();
5.8 关联(join)
在JPQL中,大部分的情况下,使用对象属性都隐含了关联(join)。例如在以下查询中:
final Query query = entityManager.createQuery( "select o from Order o where o.address.streetNumber=2000 order by o.id");
当这个句JPQL编译成以下的SQL时就会自动包含了关联,JPQL编译成SQL时关联默认取左关联(left join)。
select o.id, o.vender, o.partNumber, o.amount, addressTable.id, addressTable.streetNumber
from orderTable as o left join addressTable where addressTable.streetNumber = 2000
但在一些情况下,我们仍然需要对关联做精确的控制。因此JPQL仍然支持和SQL中类似的关联语法:
- left out join/left join
- inner join
- left join/inner join fetch
left join, left out join等义,都是允许符合条件的右边表达式中的Entiies为空。
// 返回所有地址为2000的Order纪录,不管Order中是否有OrderItem
final Query query = entityManager.createQuery( "select o from Order o left join o.orderItems where o.address.streetNumber=2000 order by o.id");
由于JPQL默认采用left join。这样的查询和以下的JPQL其实是等价的。
// 返回所有地址为2000的Order纪录,不管Order中是否有OrderItem
final Query query = entityManager.createQuery( "select o from Order o where o.address.streetNumber=2000 order by o.id");
需要显式使用left join/left outer join的情况会比较少。inner join要求右边的表达式必须返回Entities。
// 返回所有地址为2000的Order纪录,Order中必须有OrderItem
final Query query = entityManager.createQuery( "select o from Order o inner join o.orderItems where o.address.streetNumber=2000 order by o.id");
left/left out/inner join fetch提供了一种灵活的查询加载方式来提高查询的性能。在默认查询中,Entity中的集合属性默认不会被关联,集合属性默认是缓加载( lazy-load )。
// 默认JPQL编译后不关联集合属性变量(orderItems)对应的表
final Query query = entityManager.createQuery( "select o from Order o inner join o.orderItems where o.address.streetNumber=2000 order by o.id");
final List result = query.getResultList();
// 这时获得Order实体中orderItems( 集合属性变量 )为空
final Order order = (Order)result.get( 0 )
// 当应用需要时,EJB3 Runtime才会执行一条SQL语句来加载属于当前Order的OrderItems
Collection orderItems = order.getOrderItems();
这样的查询性能上有不足的地方。为了查询N个Order,我们需要一条SQL语句获得所有的Order的原始/对象属性, 但需要另外N条语句获得每个Order的orderItems集合属性。为了避免N+1的性能问题,我们可以利用join fetch一次过用一条SQL语句把Order的所有信息查询出来。
// 返回所有地址为2000的Order纪录,Order中必须有OrderItem
final Query query = entityManager.createQuery( "select o from Order o inner join fetch o.orderItems where o.address.streetNumber=2000 order by o.id");
由于使用了fetch,这个查询只会产生一条SQL语句,比原来需要N+1条SQL语句在性能上有了极大的提升。
5.9比较Entity
在查询中使用参数查询时,参数类型除了String, 原始数据类型( int, double等)和它们的对象类型( Integer, Double等),也可以是Entity的实例。
final Query query = entityManager.createQuery( "select o from Order o where o.address = ?1 order by o.id");
final Address address = new Address( 2001, "foo street", "foo city", "foo province" );
// 直接把address对象作为参数。
query.setParameter( 1, address );
5.10 批量更新(Batch Update)
JPQL支持批量更新。
Query query = managerNew.createQuery("update Order as o set o.vender=:newvender, o.partNumber='fooPart' where o.vender = 'foo'");
query.setParameter("newvender", "barVender");
// update的记录数
int result = query.executeUpdate();
5.11批量删除(Batch Remove)
JPQL支持批量删除。
Query query = managerNew.createQuery("DELETE FROM Order");
int result = query.executeUpdate();
Query query = managerNew.createQuery("DELETE FROM Order AS o WHERE o.vender='redsoft'");
int result = query.executeUpdate();