EJB3持久化规范(第三章)

1         实体操作
本章介绍如何用EntityManager API来管理实体实例的生命周期和使用Query API来查询和获取实体和它的持久化状态。
1.1    EntityManager
EntityManager关联一个持久化上下文。持久化上下文是一个实体实例的集合,在这个集合中任何持久化实体标识只有唯一一个实体实例。在持久化上下文中管理实体实例和它们的生命周期。EntityManager接口定义了用于和持久化上下文进行交互的方法。EntityManager API用于创建和清除持久化实体实例,根据实体的主键标识查找实体,以及进行跨实体查询。
实体集合由持久化单元定义的给定的EntityManager实例管理。持久化单元定义了与当前应用相关的或由应用分组的所有类,在持久化单元内的类需要映射到同一个数据库中。
在本节中定义了EntityManager的接口。实体的生命周期在2.2节中描述。EntityManager和持久化单元之间的关系在第2.3节描述,进一步的详细信息在第4章中。2.3节描述实体监听器和生命周期回调方法。2.6节描述Query接口。在第5章中描述持久化单元。
1.1.1    EntityManager接口
当使用带有事务范围的持久化上下文的实体管理器时,Perist,merge,remove,flush和refresh方法必须在事务上下文中调用。如果没有事务上下文,则抛出javax.persistence.TransactionRequiredException。
Find和gerReference方法不要求在事务上下文中调用。如果使用带有事务范围的持久化上下文的实体管理器,结果实体将是脱管的;如果使用带有扩展的持久化上下文的实体管理器,那么结果实体将受管理。参加2.3节在事务外使用实体管理器。
当实体管理器打开时,从实体管理器得到的Query和EntityTransaction对象是有效的。
如果传入createQuery方法的参数不是有效的Java持久化查询语句,那么抛出IllegalArgumentException异常,或者执行查询失败。如果本地查询不是使用的数据库的有效查询或者结果集和查询的结果不匹配,那么查询执行失败并且在执行查询的时候抛出PersistenceException。PersistenceException应当封装后台的数据库异常。
如果EntityManager接口的方法抛出运行时异常,则引起当前事务回滚。
Close、isOpen、joinTransaction和getTransaction用于管理应用管理的实体管理器和它的生命周期。可以参考4.2.2章节“获取应用管理的实体管理器”。
1.1.2    使用EntityManager API的例子
1.2    实体实例的生命周期
这章描述EntityManager用于管理实体实例生命周期的方法。实体实例可以是new、managed、detached、或者removed。
新实体实例没有持久化唯一标识,还没有和持久化上下文建立关联。
受管理的实体实例具有持久化标识,和一个持久化上下文建立了关联。
脱管的实体实例具有持久化标识,但没有或不再和持久化上下文有关联。
移除的实体实例具有持久化标识,和持久化上下文有关联,它将被从数据库中删除。
下面的章节描述了生命周期操作对实体产生的影响。cascade注解元素用于迁移这些影响到相关的实体上。Cascade典型的应用在父子关系中。
1.2.1    持久化实体实例
通过调用pesist方法或者通过层级产生的持久化可以使新实体变成受管理的和持久化的。
下面的描述就是持久化操作语义应用到实体X的效果:
如果X是一个新实体,那么他变成受管理的。实体X将在事务提交之前/时或执行flush操作后保存到数据库中。
如果X是已经存在的受管理的实体,那么它将被持久化操作忽略。然而,如果X到它引用的实体间的关系被注解为cascade=PERSIST或cascade=ALL,那么持久化操作将会传递到X引用的实体。在XML配置文件中同样配置也是可以的。
如果X是一个removed实体,它变成受管理的实体。
如果X是脱管的,那么执行持久化操作就会抛出EntityExistsException,或者在flush或提交时抛出EntityExistsException或PersistenceException。
对于X引用的所有实体Y,如果指向Y的关系(字段)被注解为cascade=PERSIST或cascade=ALL,那么持久化操作应用到Y。
1.2.2    Removal
当调用remove方法或被remove方法层级到受管理的实体时,受管理的实例变成可删除的。
Remove操作应用到实体X上的语义如下所述:
如果X是新实体,那么remove操作将忽略它。然而,如果X到它引用的实体间的关系被注解为cascade=REMOVE或cascade=ALL,那么remove操作将会传递到X引用的实体。在XML配置文件中同样配置也是可以的。
如果X是受管理的,那么remove操作将使它变成可删除的。如果X到它引用的实体间的关系被注解为cascade=REMOVE或cascade=ALL,那么remove操作将会传递到X引用的实体。
如果X是脱管的,那么执行remove操作就会抛出IllegalArgumentException(或者事务提交失败)。
如果X是可删除实体,那么它被remove操作忽略。
可删除实体X将在事务提交时或在flush操作执行后从数据库中删除。
在实体被删除后,它的状态(除了生成的状态)将是remove操作被调用时刻的状态。
1.2.3    数据库同步
持久化实体的状态在事务提交时被同步到数据库中。同步会引起对持久化实体及其关联的实体的更新持久化到数据库中。
对实体状态的更新既包括新值赋予实体的持久化属性或字段,也包括持久化属性或字段值的改变。
与数据库同步不会刷新受管理实体,除非显式调用实体的refresh方法。
受管理实体间的双向关系将根据关系的持有者所持有的引用进行持久化。开发者有责任保证在实体发生改变时内存中指向关系拥有者的引用和指向反向边的引用保持一致。在单向一对一和一对多关系的情况下,开发者要保证关系的语义是恒定的(如果唯一约束(例如在1.1.8.3.1和1.1.8.5.1章节中描述的缺省映射)没有应用到O/R映射的定义上就会产生问题)。
保证关系的反向边改变时,关系的拥有者边也作相应改变是非常重要的,以便保证这些改变在被同步到数据库时不会丢失。
当事务是活动时,持久化提供商运行时也可以在其他时间执行数据库同步操作。Flush方法可以用于强制同步,它应用到那些和持久化上下文建立关联的实体上。EntityManager和Query的setFlushMode方法用于控制同步语义。FlushModeType.AUTO在2.6.2章节中定义。如果指定为FlushModeType.COMMIT,那么在事务提交时执行flush;持久化供应商可以但不要求在其他时间执行flush。如果没有活动事务,那么持久化供应商不必执行flush。
Flush操作应用到实体X上的语义如下所述:
如果X是受管理实体,它被同步到数据库。
对于X引用的所有实体Y,如果指向Y的关系被注解为cascade=PERSIST或cascade=ALL,则持久化也应用到Y。
对于X引用的所有实体Y,如果指向Y的关系没有被注解为cascade=PERSIST或cascade=ALL,那么:
如果Y是new或者removed,则执行flush操作时抛出IllegalStateExcpetion异常,并且回滚事务,或者事务提交失败。
如果Y是脱管的,同步的语义依赖于关系的拥有者。如果X拥有关系,对关系的任何改变都会同步到数据库;否则,如果Y是关系的拥有者,如何处理没有规定。
如果X是将删除的实体,那么它将从数据库中删除。不需要层级设置。
1.2.4    脱管实体
如果使用事务范围的容器管理的实体管理器(参见2.3),那么提交事务、回滚事务、清除持久化上下文、关闭实体管理器、以及序列化实体或按值传递一个实体(如在分布式应用中,通过远程接口传递等)都会产生脱管实体。
脱管实体实例一直存在于持久化上下文的外部,它们的状态不再保证和数据库一致。
在持久化上下文结束后,应用可以获取可以获取的脱管实体实例的可读取状态。可读取的状态包括:
标志不为fetch=LAZY的任何可持久化字段或属性。
任何可以被应用获取的字段或属性。
如果持久化字段或属性是关系字段,那么只有关联实例是可以获取时,才可以安全地获取关联的实例的状态。可获取的实例包括:
任何用find()方法获取的实体实例。
任何用查询获取的或显式地在FETCH JOIN语句中要求的实体实例。
任何可被应用获取的实体实例,这些实体实例的变量持有非主键的持久化状态。
任何可以通过标记为fetch=EAGER(即非LAZY方式)的迁移关系从其他实体实例间接获取的实体实例。
1.2.4.1         合并(Merge)脱管实体状态
Merge操作使得脱管实体转变成被EntityManager管理的持久化实体。
Merge操作的语义用实体X说明如下:
如果X是脱管实体,那么它被复制到一个和它具有相同标识的预先存在的受管理实体实例X’,或者创建一个新的X的复制品。
如果X是新实体实例,那么会创建一个新的受管理的实体实例X’,并且X的状态会被复制到X’。
如果X是一个被删除实体实例,在merge操作中将抛出IllegalArgumentException(或者事务提交失败)。
如果X是受管理实体,merge操作将忽略它,但是,merge操作将被层级传递到X引用的标志有cascade=MERGE或cascade=ALL实体上。
对所有X引用的标志为cascade=MERGE或cascade=ALL的实体,Y将被迭代成Y’。对X引用的这些Y,X’将引用相应的Y’。(注意,如果X是受管理的,那么X和X’是同一个对象)。
如果X引用的所有Y没有标志为cascade=MERGE或cascade=ALL,那么X’会产生一个对受管理实体Y’的引用,这个Y’与Y有相同的标识。
持久化提供商不必merge标记为LAZY且还没有被读取的字段,在merge时,必须忽略这些字段。
持久化运行时实现在merge时或/和在flush或在提交时,必须检查任何被实体使用的Version列。没有Version列时,在merge时不需要执行检查。
1.2.4.2         脱管实体和懒惰加载
当使用懒惰的属性或字段和/或关系时,不同的提供商序列化实体和merge实体到持久化上下文可以不同。
供应商需要支持在分离的JVM间对脱管实体实例的序列化和反序列化,以及merge(这些实例可以包含还没有加载的懒惰的属性或字段和/或关系),在这些JVM中,运行时实例既可以获取实体类,也可以获取持久化实现的类。
当多个供应商之间协作时,应用不要使用懒惰加载。
1.2.5    管理实体实例
应用应当确保实例只在一个持久化上下文中。没有规定同一个Java实例在多个持久化上下文中如何处理。
Contains()方法用于确定一个实体实例是否被当前的持久化上下文管理。
在下列情况下,Cotains方法返回true:
如果实体已经从数据库中取出,但还没有被删除或脱管。
如果实体实例是新的,并且已经调用persist方法,或者持久化操作层级到这个实体。
在下列情况下,contains方法返回false:
如果实例已经脱管。
如果已经调用remove方法,或者remove操作层级到这个实体上。
如果实体是新的,但persist方法还没有被调用,或者persist方法还没有层级到这个实体上。
注意:contains方法会立即知道perist或remove方法的层级影响,但是对数据库的真正的插入、删除在事务的最后才真正产生。
1.3    持久化上下文的生命周期
容器管理的持久化上下文的生命周期既可以是在事务的范围内(事务范围的持久化上下文),也可以超出一个单个事务的范围(扩展的持久化上下文)。PersistenceContextType为容器管理的实体管理器枚举了持久化上下文的生命周期范围。当EntityManager被创建时(显式地,或通过注入,或通过JNDI)定义持久化上下文的生命周期范围。参见4.6章节。
缺省情况下,容器管理的实体管理器的持久化上下文的生命周期和事务的范围一致,即它的类型是PersistenceContextType.TRANSACTION。
当使用扩展的持久化上下文时,扩展的持久化上下文的生命周期从EntityManager实例被创建开始直到EntityManager被关闭。这个持久化上下文可以跨越EntityManager的多个事务和非事务调用。当EntityManager在事务的范围内被调用时或当绑定了扩展的持久化上下文的有状态会话bean在事务的范围内被调用时,当前事务征用扩展的持久化上下文。
具有扩展的持久化上下文的EntityManager在事务提交后维护对实体对象的引用。这些对象仍然被EntityManager管理,并且它们可以在事务之间作为受管理对象被更新(注意,当开始新事务时,在扩展的持久化上下文中的受管理对象不会从数据库中被再次加载)。来自在扩展的持久化上下文中的受管理对象的迁移会产生一个或多个其他受管理对象,而不管事务是否是活动的。
当使用具有扩展的持久化上下文的EntityManager时,persist、remove、merge和refresh操作都可以被调用而不管事务是否是活动的。当扩展的持久化上下文在事务中被征用且事务提交时,这些操作的影响才被提交。
应用管理的实体管理器的持久化上下文的范围是可扩展的。应用负责管理这个持久化上下文的生命周期。
扩展的持久化上下文在4.7章节中进一步描述。
1.3.1    事务提交
当事务提交时,在事务范围的持久化上下文中受管理实体变成脱管的;在扩展的持久化上下文中的受管理实体仍然受管理。
1.3.2    事务回滚
对事务范围的持久化上下文和扩展的持久化上下文,事务回滚引起所有已经存在的受管理实例和被删除的实例(这些实例在事务开始时被持久化到数据库中)变成脱管的。实例的状态将是事务回滚点的状态。典型地,事务回滚引起持久化上下文在回滚点处于不一致状态。特殊情况下,版本属性的状态和生成的状态(例如,生成的主键)可以是不一致的。因此,可以用和其他的脱管实体对象使用的方式一样的方式不重用以前被持久化上下文管理的实例(包括在事务中被持久化的新实例)——例如,不能传递到merge操作时(没有规定在事务回滚后那些没有被持久化到数据库的实例是作为新实例还是作为脱管实例。这由实现决定)。
1.4    乐观锁和并发
规范假定使用乐观锁。这假定实现将使用read-committed隔离(或提供商提供的隔离,在这个隔离中不使用长时间读取锁)来获取持久化单元映射的数据库,并且典型地只在调用flush方法时回写到数据库——通过flush模式来确定应用或持久化提供上运行时是否显式调用flush方法。如果事务是活动的,符合本规范的实现可以立即回写到数据库(例如,更新、创建和/或删除受管理实体),然而,实现是否通过配置来决定这种立即写数据库不在本规范的范围。配置设定乐观锁模式在2.4.3章节中描述。实际上,使用乐观锁的应用可以要求数据库隔离级别高于read-committed。然而设定数据库隔离级别不在本规范范围之中。
1.4.1    乐观锁
乐观锁是一种用于保证更新到数据库的数据和实体的状态一致的一种技术。这种技术只有当没有并发事务更新实体状态的数据时(由于实体状态只被读取)使用。这保证了更新和删除的数据和数据库目前的状态一致,并且保证不会丢失并发更新。引起违反这样的约束的事务将抛出OptimistaicLockException,并且引起事务回滚。
希望使用乐观锁的可移植应用必须为实体指定Version属性——例如,用Version注解符注解持久化属性或字段或在XML配置文件中将属性指定为version属性。强烈建议应用为那些可能被从断链的状态并发读取或合并的所有实体使用乐观锁。使用乐观锁失败可能导致实体状态不一致,丢失更新和其他状态混乱。如果乐观锁没有作为实体状态的一部分,那么应用必须承担维护数据一致性的责任。
1.4.2    版本属性
持久化提供商用Version字段或属性执行乐观锁。持久化提供商在执行实体实例生命周期操作的过程中获取或设定Version字段或属性。如果实体有一个映射为Version的属性或字段,那么实体自动地可以使用乐观锁。
实体可以获取版本属性或字段的状态,也可以为应用暴露获取版本的方法,但是不能更改版本的值(然而,批更新语句可以设置版本属性的值。参见3.10)。只有持久化提供商可以设置或更新版本属性的值。
当实体被写到数据库时,持久化提供商运行时更新版本属性。所有的非关系字段和属性,以及实体拥有的所有关系都包括在版本检查中。
当实体被合并时,merge操作的实现必须检查版本属性,如果发现要被merge的对象是实体的过时拷贝,那么抛出OptimisticLockException——例如,由于实体变成脱管实体,这个实体已经被更新。依赖实现使用的策略,可能在flush被调用时或提交时才抛出异常,不管哪一个先发生。
当执行乐观锁检查时,要求持久化提供商只使用版本属性。持久化提供商可以还可以提供额外的机制。然而,实现额外的机制不在本规范规定之内(这些额外机制可能在这个规范的未来版本中标准化)。
如果只有一部分实体包含版本属性,要求持久化提供商检查指定了版本属性的实体。但不保证对象图的一致性,没有指定版本属性的实体不会在完成之前停止操作。
1.4.3    锁模式
除了上面描述的语义外,通过EntityManager的lock方法可以进一步指定锁模式。
定义了两个锁模式:READ和WRITE:
请求锁类型LockModeType.READ和LockModeType.WRITE的语义如下:
如果事务T1在版本对象上调用lock(entity, LockModeType.READ),那么实体管理器必须保证不要发生下面的现象:
P1(脏读):事务T1更改一行。在T1提交或回滚之前,另一个事务T2读这一行并且获得更改后的值。事务T2最终成功提交,不管T1是否提交或回滚,也不管是在T2提交之前还是之后提交或回滚。
P2(非重复读):事务T1读取一行。在T1提交之前,事务T2更改或删除了那一行。两个事务最终都提交成功。
这通常由实体管理器完成在后台数据库行上加锁。这样的锁可以被立刻得到(一直保持到提交完成),或者可以在提交时获得这样的锁(即使这样,也必须保持到提交完成)。只要能避免上述现象发生,任何支持重复读的实现都是允许的。
不要求持久化实现支持对非版本对象调用lock(entity, LockModeType.READ)。当实现不支持这种调用时,它必须抛出PeristenceException。当支持这种调用时,不管是否是版本或非版本对象,LockModeType.READ必须总是阻止P1和P2现象的发生。对非版本对象调用lock(entity, LockModeType.READ)的应用将是不可移植的。
如果事务T1在版本对象上调用lock(entity, LockModeType.WRITE),实体管理器必须避免现象P1和P2发生(对LockModeType.READ同样)和也必须强迫对实体的版本列进行增量更新。强迫的版本更新可以立即执行,也可以延迟到flush或commit时。如果实体在延迟的版本更新被应用之前被删除,则由于后台的数据库行不再存在,因此强迫的版本更新被忽略。
不要求持久化实现支持在非版本对象上调用lock(entity, LockModeType.WRITE)。当实现不支持这种调用时,它必须抛出PeristenceException。当支持这种调用时,不管是否是版本或非版本对象,LockModeType.WRITE必须总是阻止P1和P2现象的发生。对非版本对象来说,LockModeType.WRITE是否有附加的行为由提供商决定。对非版本对象调用lock(entity, LockModeType.READ)的应用将是不可移植的。
对版本对象,允许实现在请求LockModeType.READ的地方使用LockModeType.WRITE,但不能反过来。
否则,如果版本对象被更新或删除,那么实现必须保证满足LockModeType.WRITE的要求,即使没有显式调用EntityManager.lock。
对可移植应用来讲,应用不应当依赖特定供应商的配置来通过其他方式保证(非EntityManager.lock)重复读。然而,应当注意:如果实现在前面已经获得数据库行上的悲观锁,那么实现可以忽略也可以不忽略在代表这些行的实体对象上的lock(entity, LockModeType.READ)调用。
1.4.4    OptimisticLockException
当用flush模式来设置一致性时,实现可以延迟写数据库到事务结束时。在这种情况下,乐观锁检查可以一直到提交时发生,并且可以在提交完成之前的阶段内抛出OptimisticLockException。如果应用必须捕捉或处理OptimisticLockException,那么应用必须应当使用flush模式来迫使发生写数据库。这样,应用就可以捕捉和处理乐观锁异常。
OptimisticLockException提供了一个返回引起异常的对象的API。不能保证每次抛出异常时都呈现这个对象引用,但无论什么时候持久化提供商都能提供它。应用不能依赖这个可以获得的对象。
在一些情况下,OptimisticLockException可以被抛出,并且被其他异常封装,例如在穿越VM边界时,封装为RemoteException。在封装的异常内引用的实体应当是可序列化的。
OptimisticLockException总是引起事务回滚。
在新事务内刷新或重新加载对象然后再使用这个事务可能会引起OptimisticLockException。
1.5    实体监听器和回调方法
一个方法可以被声明成回调方法,以便它可以接收实体寿命周期事件的通知。
生命周期回调方法可以定义在实体类、被映射的超类或者和实体/被映射超类关联的实体监听器上。实体监听器类的方法用于响应实体的生命周期事件。在实体类或被映射超类上可以定义多个实体监听器类。
缺省的实体监听器(应用到持久化单元内的所有实体上的实体监听器)可以用XML配置文件指定。
用元语注解符或XML配置符定义生命周期回调方法和实体监听器类。当使用注解符时,可以在实体类或被映射超类上用EntityListeners注解符定义一到多个实体监听器类。如果指定多个实体监听器类,他们的调用顺序由它们在EntityListeners中的顺序决定。XML配置符是指定实体监听器类调用顺序的可选方案或用于覆盖注解符内指定的顺序。
在实体类、被映射的超类或监听器类上可以指定任何注解符的子集或组合。对同一个生命周期事件,一个单独的类不可以有多个生命周期回调方法。但同一个方法可以用于多个回调事件。
可以在类层次上的多个实体类和被映射超类上直接定义监听器类和/或生命周期方法。2.5.4章节描述了在这种情况下方法调用顺序的规则。
实体监听器类必须有一个public的无参构造器。
实体监听器类是无状态的。实体监听器类的生命周期没有规定。
回调需遵循以下规则:
回调方法可以抛出unchecked/运行时异常。在事务内执行的回调方法抛出的运行时异常引起事务回滚。
回调可以调用JNDI、JDBC、JMS和企业Bean。
通常情况下,可移植应用不应当在生命周期回调方法内调用EntityManager或Query操作、获取其他实体实例或更改关系。(这些操作的语义在规范未来的版本中可能被标准化)
当在Java EE环境内调用时,回调监听器共享将要调用的组件的企业命名上下文,并且回调方法在将要调用的组件的事务和安全上下文中被调用。(例如,如果事务属性是RequiredsNew的会话bean的业务方法正常终止引起的事务提交,那么将在组件的命名上下文中、事务上下文和安全上下文中执行PostPersist和PostRemove回调方法)
1.5.1    生命周期回调方法
实体生命周期回调方法可以定义在实体监听器类上,也可以直接定义在实体类或被映射超类上。
生命周期回调方法用注解符指派调用它们的回调事件或用XML配置符指派它们对应的回调事件。
用于实体类或被映射超类的回调方法的注解符和用于实体监听器类中的回调方法的注解符是一样的。然而私有方法的符号是不同的。
在实体或被映射超类内定义的回调方法形式如下:
在监听器内定义的回调方法形式为:
Object参数是回调方法被调用的实体实例,它可以是被声明为真正的实体类型。
回调方法的可见性可以是public,private,protected或包级可见,但不应当是static或final。
下面的注解符用于指派对应生命周期事件的回调方法。
PerPersist
PostPersit
PreRemove
PostRemove
PreUpdate
PostUpdate
PostLoad
1.5.2    实体的生命周期回调方法的语义
PrePersist和PreRemove回调方法在EntityManger的persist和remove方法被执行之前调用。对那些已经执行merge操作并且产生新的受管理实例的实体来说,PrePersist回调方法会在实体的状态复制到受管理实例之后被调用。PrePersist和PreRemove方法也会在所有被层级到的实体上调用。PrePersist和PreRemove方法总是作为persist、merge和remove操作的一部分被同步调用。
PostPersist和PostRemove回调方法在EntityManger的persist和remove方法被执行之后调用。这些方法也会在所有被层级到的实体上调用。PostPersist和PostRemove方法将在数据库的插入和删除操作被执行之后被调用。可以在persist、merge或remove操作执行之后直接调用上述的数据库操作,也可以在flush操作被调用后直接调用(这可能在事务结束时)。可以在PostPersist方法内获得生成的主键值。
PreUpdate和PostUpdate回调方法在数据库更新操作之前和之后调用。调用发生在实体状态被更新到数据库时或状态被flush到数据库时(在事务的结束时刻)。
注意:当实体在一个事务内先持久化,接着被更新时,或者在一个事务内先被更改,然后被删除时,是否发生PreUpdate 和PostUpdate 回调由具体的实现决定。可移植的应用不应该依赖这些行为。
PostLoad方法在实体从数据库中加载到当前持久化上下文中时,或者在refresh操作执行之后被调用。PostLoad方法在查询结果返回或获取之前,或者在关系被层级加载之前调用。
是否在生命周期事件层级的前面或后面调用回调方法依赖于具体的实现。应用不应当依赖于这个顺序。
1.5.3    举例
1.5.4    为一个实体生命周期事件定义多个生命周期回调方法
如果一个实体生命周期事件上定义多个生命周期回调方法,那么这些方法的调用顺序如下。
任何情况下,缺省的监听器是第一个被调用,顺序按照在XML描述文件中定义的顺序。除非显式的通过ExcludeDefaultListeners注释符或exclude-default-listeners元素排除缺省监听器,缺省的监听器都将应用到持久化单元的所有实体上。
定义在实体监听器类或被映射的超类上的生命周期回调方法的调用顺序和在EntityListeners注释符中指定的实体监听器类的顺序一致。
如果在继承层次中的多个类(实体类和/或被映射的超类)都定义了实体监听器,那么定义在超类上的实体监听器类先于定义在子类上的实体监听器类。ExcludeSuperclassListeners注释符或exclude-superclass-listeners元素用于排除定义在超类或被映射的超类上的实体监听器。被上述元素排除掉的监听器将不会包含在实体类及其子类中(注:被排除的监听器可以通过显式的在EntityListeners注释符或entity-listeners元素中指定来被重新引入)。ExcludeSuperclassListeners注释符或exclude-superclass-listeners元素不会排除将缺省的实体监听器。
如果对同一个生命周期事件的一个生命周期回调方法也被指定到实体类和/或一个或多个实体或被映射的超类上,那么在实体类和/或超类上指定的回调方法在其他回调方法之后被调用,且超类的首先被调用。一个类可以重载父类的相同类型的回调方法,且在这种情况下,重载的方法不会被调用(注:注意如果一个方法重载了父类的回调方法,但指定了不同的生命周期事件或不是一个生命周期回调方法,那么这个重载的方法将被调用)。
持久化提供者按照指定的顺序调用回调方法。如果前一个回调方法正常执行,那么持久化提供者执行下一个回调方法。
XML描述符可以用于覆盖在注释符中指定的生命周期回调方法的执行顺序。
1.5.5    举例
为动物定义了几个实体类和监听器:
如果PostPersist事件发生在Cat实例上,则按顺序调研下面的方法:
假定,SiameseCat是Cat的子类:
如果PostPersist事件发生在SiameseCat实例上,则按顺序执行下列方法:
假定SiameseCat的定义改为下面的方式:
将按照下面的顺序调用下面的方法,其中postPersistAnimal是定义在SiameseCat类上的PostPersist方法:
1.5.6    异常
生命周期回调方法可以抛出运行时异常。在一个事务内执行的回调方法内抛出的运行时异常引起事务回滚。在抛出运行时异常后,其他的回调方法将不再被调用。
1.5.7    在XML描述符中的回调监听器类和生命周期方法规范
XML描述符是注释符的可选方案,或用于覆盖在注释符中指定的回调方法的调用顺序。
1.5.7.1         回调监听器规范
Entity-listener用于指定实体监听器类的监听器方法。通过pre-persist,post-persist,per-remove,post-remove,pre-update,post-update和/或post-load元素来指定监听器方法。
不管是用XML描述符还是与注释符组合使用,一个实体监听器类上最多只能有一个pre-persist方法,一个post-persit方法,一个pre-remove方法,一个post-remove方法,一个pre-update方法,一个post-update方法和/或post-load方法。
1.5.7.2         实体监听器类绑定到实体的规范
Entity 或 mapped-superclass元素的Entity-listeners子元素用于为单独的实体或被映射超类及其子类指定实体监听器类。
实体监听器绑定到实体类是递增的。绑定到实体或被映射超类的超类上的实体监听器类也应用到实体或被映射超类上。
Exclude-superclass-listener元素指定不调用在父类上定义的监听器方法。
Exclude-default-listener元素指定不调用的缺省监听器方法。
为一个实体或被映射超类显式列出排除的缺省或超类监听器会影响到实体或被映射超类及其子类。
在为一个生命周期事件指定多个回调方法的情况下,使用在2.5.4章节的调用顺序规则。
1.6    Query API
Query API既可以用于静态查询(如,命名查询),也可以用于动态查询。Query API致词后命名参数绑定和分页控制。
1.6.1    Query接口
package javax.persistence;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
 * Interface used to control query execution.
 */
public interface Query {
/**
 * Execute a SELECT query and return the query results
 * as a List.
 * @return a list of the results
 * @throws IllegalStateException if called for a Java
 *Persistence query language UPDATE or DELETE statement
 */
public List getResultList();
/**
 * Execute a SELECT query that returns a single result.
 * @return the result
 * @throws NoResultException if there is no result
 * @throws NonUniqueResultException if more than one result
 * @throws IllegalStateException if called for a Java
 *Persistence query language UPDATE or DELETE statement
 */
public Object getSingleResult();
/**
 * Execute an update or delete statement.
 * @return the number of entities updated or deleted
 * @throws IllegalStateException if called for a Java
 *Persistence query language SELECT statement
 * @throws TransactionRequiredException if there is
 *                  no transaction
 */
public int executeUpdate();
/**
 * Set the maximum number of results to retrieve.
 * @param maxResult
 * @return the same query instance
 * @throws IllegalArgumentException if argument is negative
 */
public Query setMaxResults(int maxResult);
/**
 * Set the position of the first result to retrieve.
 * @param start position of the first result, numbered from 0
 * @return the same query instance
 * @throws IllegalArgumentException if argument is negative
 */
public Query setFirstResult(int startPosition);
/**
* Set an implementation-specific hint.
 * If the hint name is not recognized, it is silently ignored.
 * @param hintName
 * @param value
 * @return the same query instance
*@throwsIllegalArgumentExceptionifthesecondargumentisnot
 *valid for the implementation
 */
public Query setHint(String hintName, Object value);
/**
 * Bind an argument to a named parameter.
 * @param name the parameter name
 * @param value
 * @return the same query instance
 * @throws IllegalArgumentException if parameter name does not
 *correspond to parameter in query string
 *or argument is of incorrect type
 */
public Query setParameter(String name, Object value);
/**
 * Bind an instance of java.util.Date to a named parameter.
 * @param name
 * @param value
 * @param temporalType
 * @return the same query instance
 * @throws IllegalArgumentException if parameter name does not
 *correspond to parameter in query string
 */
public Query setParameter(String name, Date value, TemporalType
temporalType);
/**
 * Bind an instance of java.util.Calendar to a named parameter.
 * @param name
 * @param value
 * @param temporalType
 * @return the same query instance
 * @throws IllegalArgumentException if parameter name does not
 *correspond to parameter in query string
 */
public Query setParameter(String name, Calendar value, Temporal-
Type temporalType);
/**
 * Bind an argument to a positional parameter.
 * @param position
 * @param value
 * @return the same query instance
 * @throws IllegalArgumentException if position does not
 *correspond to positional parameter of query
 *or argument is of incorrect type
 */
public Query setParameter(int position, Object value);
/**
 * Bind an instance of java.util.Date to a positional parameter.
 
* @param position
 * @param value
 * @param temporalType
 * @return the same query instance
 * @throws IllegalArgumentException if position does not
 *correspond to positional parameter of query
 */
publicQuerysetParameter(intposition,Datevalue,TemporalType
temporalType);
/**
 * Bind an instance of java.util.Calendar to a positional param-
eter.
 * @param position
 * @param value
 * @param temporalType
 * @return the same query instance
 * @throws IllegalArgumentException if position does not
 *correspond to positional parameter of query
 */
publicQuerysetParameter(intposition,Calendarvalue,Temporal-
Type temporalType);
/**
 * Set the flush mode type to be used for the query execution.
 * The flush mode type applies to the query regardless of the
 * flush mode type in use for the entity manager.
 * @param flushMode
 */
public Query setFlushMode(FlushModeType flushMode);
}
 
一个由多个select表达式组成SELECT语句的查询的结果是Object[]。如果SELECT语句只有一个select表达式,查询结果是Object。当使用本地SQL查询时,SQL查询结果集映射(看2.6.6章节)决定了返回多少项(实体,标量值等)。如果返回多个项,查询结果是Object[]。如果只返回一项或指定了结果的类,则查询结果是Object。
如果参数的名称和查询语句内的命名参数不一致,或者如果指定的位置值和查询语句内的位置参数不一致,或者参数的类型不匹配,则抛出IllegalArgumentException。可以在参数绑定的时候抛出异常,或者执行查询失败。
在涉及多个表连接的查询上使用setMaxResults或setFirstResult的影响没有定义。
除了executeUpdate方法外的查询方法不要求在一个事务上下文中调用。尤其getResultList和getSingleReslut方法不要求在一个事务上下文中调用。如果使用带事务范围持久化上下文的实体管理器,则查询的结果实体是托管的;如果使用带扩展持久化上下文的实体管理器,则查询结果实体会受到管理。参照第4章的在事务外部使用实体管理器和持久化上下文类型。
除了NoResultException和NonUniqueResultException以外其他的由Query接口的方法抛出的运行时异常都会引起当前事务回滚。
1.6.1.1         举例
1.6.2    查询和FlushMode
Flush模式影响下列查询的结果。
当在事务内执行查询时,如果在查询对象上设置FlushModeType.AUTO,或持久化上下文的flush模式被设置为AUTO(缺省值)且Query对象上没有指定flush模式,那么持久化提供商必须保证查询操作能够知道对持久化上下文内实体的所有更新(这些更新可能影响查询结果)。持久化实现可以通过flush这些实体到数据库或通过别的方式实现这个目的。如果设置FlushModeType.COMMIT,那么持久化上下文内实体的更新对查询的影响没有规定。
public enum FlushModeType {
COMMIT,
AUTO
}
如果没有活动的事务,那么持久化提供商不需要flush到数据库。
1.6.3    命名参数
命名参数是以“:”作为前缀的标识符。命名参数是大小写敏感的。
命名参数遵循在3.4.1中定义的标识符规则。命名参数可以在EJB QL中使用,但不能用在本地查询中。只有位置参数可以用于本地查询。
传入Query API的setParameter方法的参数名不包括前缀“:”。
1.6.4    命名查询
命名查询是一个静态查询表达式。命名查询可以用EJB QL语言定义,也可以用SQL语言定义。查询的名字在持久化单元内必须唯一。
下面是定义命名查询的例子:
下面是使用命名查询的例子:
1.6.5    多态查询
缺省情况下,所有的查询都是多态的。也就是说查询语句内FROM语句不仅仅是显式指定的特定的实体类的实例,也可以是它的子类。查询返回的实例包含满足条件的子类的实例。(注:在将来的版本中考虑构造受限的查询多态机制)
例如,查询:
返回所有职员的平均薪水,包含Employee的子类,例如Manager和Exempt。
1.6.6    SQL查询
可以用本地SQL表述查询语句。本地SQL的查询结果可以由多个实体,分层级的值,或者两者的组合组成。查询返回的实体可以是不同的实体类型。
提供SQL 查询是为了支持那些必须使用目标数据库本地SQL 的情况(不能使用EJB QL )。本地SQL 查询不能在多个数据库间移植。
当SQL查询返回多个实体时,实体必须与在SqlResultSetMapping元语中定义的SQL语句的结果列一一对应。然后,持久化运行时可以通过结果集映射元语将JDBC结果映射到期望的对象上。可以参照第7.3.3章节了解SqlResultSetMapping元语注释符的定义和其它相关的注释符。
如果查询结果被限定到一个实体类的多个实体,那么可以使用更加简单的格式,且可以不使用SqlResultSetMapping元语。
下面的例子解释了用createNativeQuery方法动态的创建本地SQL查询,并且将一个指定了结果类型的实体类作为参数。
当执行上面的语句后,将返回所有名字为“widget”的Order实体。也可以用SqlResultSetMapping获得相同的结果。
在这种情况下,可能要按下面的方式指定查询结果类型的元语。
下面的查询和SqlResultSetMapping解释了返回多个实体类型,并且假定使用缺省元语和缺省列名。
当返回一个实体时,SQL语句查询所有映射到实体对象的列。这应当包括关联其它实体的外键列。当返回的数据不足够(数据列没有定义的多)时,返回的结果没有规定。SQL结果集映射不必将结果与实体的非持久化字段进行映射。
在SQL结果集映射注释符中使用的列名指的是在SQL SELECT语句中使用的列名。注意,如果SQL结果中有相同名字的列时,必须在SQL SELECT语句中使用列的别名。
下例是组合多个实体类型,它在SQL语句内使用了别名,并且将这些列名显式地映射到实体的字段上。FieldResult注解用于定义这种映射。
通过指定ColumnResult注释符,可以在查询结果内包含标量的结果类型。
当返回的实体类型是单值关系的拥有者且外键是组合外键时,应当为每个外键列使用FieldResult元素。FieldResult元素必须使用“.”来表明哪个列映射到目标实体主键的哪个属性/字段。接下来描述的“.”形式除了组合主键或嵌入式主键外,在其他的用法中不要求支持。
如果目标实体有类型为IdClass的主键,那么使用:关系字段或属性的名字+“.”+目标实体主键字段或属性的名字的形式。正如在8.1.15章节所述,后者将用Id注释。
举例:
Query q = em.createNativeQuery(
    "SELECT o.id AS order_id, " +
        "o.quantity AS order_quantity, " +
        "o.item_id AS order_item_id, " +
        "o.item_name AS order_item_name, " +
        "i.id, i.name, i.description " +
    "FROM Order o, Item i " +
    "WHERE (order_quantity > 25) AND (order_item_id = i.id) AND
(order_item_name = i.name)",
        "OrderItemResults");
@SqlResultSetMapping(name="OrderItemResults",
    entities={
        @EntityResult(entityClass=com.acme.Order.class, fields={
            @FieldResult(name="id", column="order_id"),
            @FieldResult(name="quantity", column="order_quantity"),
            @FieldResult(name="item.id", column="order_item_id")}),
            @FieldResult(name="item.name",
column="order_item_name")}),
        @EntityResult(entityClass=com.acme.Item.class)
})
如果目标实体由一个类型为EmbeddedId的主键,那么使用:关系字段/属性的名字+“.”+主键的属性/字段名字(例如,注释为EmbeddedId的字段或属性的名字)+对应的被嵌入的主键类的属性或字段的名字的形式。
例子:
Query q = em.createNativeQuery(
    "SELECT o.id AS order_id, " +
        "o.quantity AS order_quantity, " +
        "o.item_id AS order_item_id, " +
        "o.item_name AS order_item_name, " +
        "i.id, i.name, i.description " +
    "FROM Order o, Item i " +
    "WHERE (order_quantity > 25) AND (order_item_id = i.id) AND
(order_item_name = i.name)",
        "OrderItemResults");
@SqlResultSetMapping(name="OrderItemResults",
    entities={
        @EntityResult(entityClass=com.acme.Order.class, fields={
            @FieldResult(name="id", column="order_id"),
            @FieldResult(name="quantity", column="order_quantity"),
            @FieldResult(name="item.itemPk.id",
column="order_item_id")}),
            @FieldResult(name="item.itemPk.name",
column="order_item_name")}),
        @EntityResult(entityClass=com.acme.Item.class)
})
用于组合外键的FieldResult元素与目标实体的EmbeddedId类结合使用。如果关系被eagerly加载,那么这种组合就可以用于读取实体。
没有定义本地查询如何使用命名参数。对于SQL查询,只有使用位置参数才能使应用可移植。
目前只支持单值关系的对连接查询。
1.7    异常汇总
下面是在规范中定义的所有异常:
1.7.1    PersistenceException
PersistenceException是当出现问题时由持久化提供商抛出。它可以是调用的操作由于不期望的错误而没有完成(例如,持久化提供商不能打开数据库连接)。
在规范中定义的其他异常都是PersistenceException的子类。除了NoReusltException和NoUniqueResultException外的PersistenceException的所有实例都会引起当前活动事务的回滚。
1.7.2    TransactionRequiredException
当要求事务,但事务是不活动时抛出TransactionRequiredException。
1.7.3    OptimisticLockException
当乐观锁发生冲突时抛出OptimisticLockException。这个异常可以在flush或提交的API调用中被抛出。如果当前是活动事务,则回滚该事务。
1.7.4    RollbackException
当EntityTransaction.commit失败时抛出RollbackException。
1.7.5    EntityExistsException
当执行persist操作但实体已经存在时抛出EntityExistsException。EntityExistsException可以在调用持久化操作时抛出,或者在提交时抛出EntityExistsException或另一个PersistenceException时抛出。
1.7.6    EntityNotFoundException
当通过getReference获取实体的引用但实体不存在时抛出EntityNotFoundException。也可以在执行刷新操作时实体在数据库中已经不存在抛出。如果当前事务是活动的,则回滚事务。
1.7.7    NoResultException
当调用Query.getSingleResult但没有结果返回时抛出NoResultException。这个异常不会引起当前活动事务的回滚。
1.7.8    NonUniqueResultException
当调用Query.getSingleResult但查询结果有多个时抛出NonUniqueResultException。这个异常不会引起当前活动事务的回滚。
 

你可能感兴趣的:(J2EE)