Hibernate实战(第2版)学习笔记四

   101.在Java SE中扩展持久化上下文
    1)默认的持久化上下文范围
    在没有EJB的JPA中,持久化上下文被绑定到EntityManager实例的生命周期和范围。为了在对话中给所有的事件重用相同的持久化上下文,只需要重用同一个EntityManager去处理所有的事件。
    2)防止自动清除
    102.使用EJB的上下文传播
    对于持久化上下文的范围和传播,有正式的规则:
    (1)如果容器提供的(通过注入或者通过查找获得的)EntityManager第一次被调用,持久化上下文就开始了。默认情况下,它是事务范围的,并在系统事务提交或者回滚时关闭。它在事务提交时被自动清除。
    (2)如果容器提供的(通过注入或者通过查找获得的)EntityManager第一次被调用,持久化上下文就开始了。如果此时没有系统事务是活动的,持久化上下文就很短,并只服务于单独的方法调用。被任何一种方法触发的任何SQL都在自动提交模式下在一个数据库连接中执行。在这个EntityManager调用中(可能)获取的所有实体实例都立即变成脱管状态。
    (3)如果无状态的组件被调用,并且调用者有一个活动的事务并被传播到调用的组件,绑定到JTA事务的任何持久化上下文就通过事务传播。
    (4)如果无状态的组件被调用,且调用者没有活动的事务,或者事务没有传播到被调用的组件,当在无状态的组件内部调用EntityManager时,就创建了新的持久化上下文。换句话说,如果没有事务被传播,就不会发生持久化上下文的传播。
    如果你想要利用EJB和被扩展的持久化上下文实现一个对话,有两种选择:
    (1)可以编写一个有状态的会话bean作为对话控制器。持久化上下文可以被自动界定到这个有状态bean的生命周期,这是一种方便的方法。当这个有状态的EJB被删除时,持久化上下文就关闭。
    (2)可以用EntityManagerFactory自己创建EntityManager。这个EntityManager的持久化上下文是应用程序脱管的——你必须手工清除和关闭它。如果在JTA事务范围内部调用它,还必须用joinTransaction()操作通知EntityManager。使用有状态的会话bean的第一种策略通常更好。
    表11-1 EJB3.0声明式事务属性类型
    属性名称                                                              描述
 (1)REQUIRED      必须通过事务上下文调用的一种方法。如果客户端没有事务上下文,容器就会启动事务,并获取这个事务所用的所有资源(数据源等)。如果这个方法调用其他的事务组件,事务就会被传播。方法返回时,容器在结果被发送到客户端之前提交事务。
(2)NOT_SUPPORTED 如果方法在从客户端传播的事务上下文内部调用,调用者的事务就被暂停,并在方法返回时重新激活。如果调用者没有事务上下文,就没有给该方法启动任何事务。不是全部所用的资源都通过事务的到获取(发生自动提交)。
(3)SUPPORTS 如果方法在从客户端传播的事务上下文内部调用,它就把这个事务上下文与跟REQUIRED相同的结果联结起来。如果调用者没有事务上下文,就没有事务被启动,结果与NOT_SUPPORTED的相同。这个事务属性类型应该只用于可以正确处理这两种情况的方法。
(4)REQUIRES_NEW 方法始终在新的事务上下文内部执行,结果和行为与使用REQUIRED的相同。任何被传播的客户端事务都被暂停,并在方法返回且新事务完成时继续
(5)MANDATORY 方法必须通过一个活动的事务上下文被调用。然后它联结这个事务上下文,并且在需要时进一步传播。如果调用时没有出现事务上下文,就抛出异常
(6)NEVER 它与MANDATORY相反。如果方法通过一个活动的事务上下文被调用,就抛出异常。
103.当利用有状态的EJB设计对话时,或者如果你想要混合无状态和有状态的组件时,必须知道事务上下文和持久化上下文的传播规则:
 (1)如果具有被扩展的持久化上下文的有状态会话bean调用(实际上实例化)了另一个也有持久化上下文的有状态会话bean,第二个有状态会话bean就从调用这处继承持久化上下文。持久化上下文的生命周期由第一个有状态会话bean决定;它在两个会话bean都被移除时关闭。如果设计更多有状态的会话bean,这个行为就是递归的。这个行为还不依赖于任何事务规则和事务传播。
 (2)如果EntityManager用于有着绑定的被扩展持久化上下文的有状态会话bean的一个方法中,并且这个方法需要/支持客户端的JTA事务,那么如果方法的调用程序也通过它的事务传播不同的持久化上下文,就会出现异常。
104.传播性持久化
    当应用程序操作持久化的对象网络时,结果可能会是一张由持久化的、脱管的和瞬时的实例组成的对象图。传播性持久化是一种允许你把持久化自动传播到瞬时的和脱管的子图的方法。
    (1)按可到达性持久化
    每当应用程序从已经持久化的实例中创建对其中一个实例的引用时,如果任何实例变成了持久化,对象持久层便是在实现按可到达性持久化。
    所有可以从持久化实例到达的对象都在原始实例被变成持久化时、或者就在内存状态与数据库通不值钱变成持久化。
    按可到达性持久化保证了参照完整性:任何对象图都可以通过加载持久化根对象被完全重新创建。应用程序可以逐个关联地穿过对象图,从来不必担心实例化的持久化状态。(SQL数据库有一种不同的参照完整性的方法,它依赖声明和过程的约束来侦测行为异常的应用程序)。
    按可到达性持久化最多是个不彻底的解决方案。它帮助你使瞬时对象变成持久化,并把它们的状态传播到数据库,而不需要对持久化管理器的诸多调用。然而,至少在SQL数据库和ORM的上下文中,它并不是使持久化对象变成瞬时(把它们的状态从数据库移除)这个问题的完整解决方案。
    (2)把级联应用到关联
    Hibernate的传播性持久化模型使用与按可到达持久化相同的基本概念:检查对象关联来确定传播性状态。此外,Hibernate还允许你给每个关联映射指定一种级联样式(cascade style),它为所有的状态转变提供了更多灵活性和更细力度的控制。Hibernate读取被声明的样式,并自动把操作级联到被关联的对象。
    关联的非反向端被用来在数据库中生成管理关联的SQL语句(若干外键列的插入和更新)。级联启用了跨实体类关联的传播性对象状态变化。
    (3)用传播性持久化保存几个新实例
    Hibernate如何发现哪个实例是旧的以及哪个实例是新的呢?有许多选项可用。Hibernate假设实例是未被保存的瞬时实例,如果:
    (1)标识符属性为null
    (2)版本或者时间戳属性(如果存在的话)为null
    (3)同一个持久化的新实例(由Hibernate内部创建)有着与指定实例相同的数据库标识符值。
    (4)你在映射文档中给类提供一个unsaved-value,并且标识符属性的值匹配。unsaved-value属性对于版本和时间戳映射元素也可用。
    (5)包含相同标识符的实体数据不再二级高速缓存中。
    (6)你提供org.hibernate.Interceptor的一个实现,并在检查完你自己代码中的实例之后,从Interceptor.isUnsaved()返回Boolean.TRUE。
    (4)考虑传播性删除
    通过启用孤儿删除,可以从集合中移除孤儿,并且Hibernate将假设它们不再被任何其他的实体引用。再次提醒,如果映射一个组件的集合,孤儿删除就是隐式的;额外的选项只与实体引用的集合相关(通常是<one-to-many>)。
105.利用JPA的传播性关联
    Java Persistence规范对于启动了级联对象操作的实体关联支持注解。就像在元素的Hibernate中一样,每个EntityManager操作都有一个相当的级联样式。
    merge()与重附不同:在合并之后,它返回一个新值(它应该被绑定到作为句柄的当前变量)到当前状态。
106.大批量和批量操作
    需要大量数据的操作最好不要再应用层中执行。你应该把操作移近数据的位置,而不是其他地方。在SQL系统,DML语句UPDATE和DELETE直接在数据库中执行,如果必须实现一个涉及上千行的操作,这些通常就足够了。更复杂的操作可能需要更复杂的过程在数据库内部运行;因此,应该把存储过程当做一种可能的策略。
(1)使用HQL和JPA QL的大批量语句
    HQL类似于SQL。这两者之间的区别在于HQL使用类名称而不是表名称,使用属性名称而不是列名称。它也理解继承——也就是说,无论你正在使用超类还是接口进行查询。
    JPA查询语言,就像JPA和EJB3.0定义的一样,是HQL的一个子集。
    如果执行一个直接在数据库中的行上操作的SQL语句,那么所做的任何改变都不影响内存对象(无论它们可能处于什么状态)。任何直接的DML语句都绕过了Hibernate持久化上下文(和所有高速缓存)。
    避免这个问题的务实解决方案是一个简单的会话:首先在一个新的持久化上下文中执行任何直接的DML操作。然后,用Hibernate Session或者EntityManager加载和保存对象。这个会话保证持久化上下文不受任何之前执行语句的影响。另一种方法是,如果你知道它已经在持久化上下文背后被修改了的话,可以选择性地使用refresh()操作从数据库中重载持久化对象的状态。
    如果你在HQL或JPAQL使用了别名,所有的属性都必须加上别名作为前缀。还要注意,HQL(和JPA QL)UPDATE语句可以只引用单个实体类;例如你无法编写单个语句来同时更新Item和CreditCard对象。WHERE子句中允许子查询;只有在这些子查询中才允许任何联结。
    默认情况下,直接的DML操作对于被影响实体的任何版本或者时间戳值(这在Java Persistence中被标准化了)没有影响。然而,通过HQL,你可以增加直接被修改的实体实例的版本号。   
    就像SQL大批量操作一样,HQL(和JPA QL)大批量操作不影响持久化上下文,它们绕过任何高速缓存。
    注意INSERT ... SELECT只可用于HQL;JPA QL没有标准化这种语句。
(2)利用批量处理
    注意,应该给任何批量操作禁用二级高速缓存;否则,批量过程期间对象的每一次修改都必须为这个持久化类传播到二级高速缓存。
    禁用持久化上下文及使用StatelessSession接口有一些其他的严重后果和概念上的限制(至少,如果你与一般的Session比较的话):
  •     StateLessSession没有持久化上下文高速缓存,没有与任何其他的二级或者高速缓存交互。拟进行的每一件事情都导致立即的SQL操作。
  •     对于对象的修改不会被自动侦测(没有脏检查),并且SQL操作也不会被尽可能迟的执行(没有迟些)。
  •     你调用的所有对象的修改和操作都没有被级联到任何被关联的实例。你正在使用单个实体类的实例。
  •     对于被映射为实体关联(一对多,多对多)的集合的任何修改都被忽略。只考虑值类型的集合。因此,你不应该把包含集合的实体关联(而只应该把包括外键的非反向端)映射为多对一;只通过一端处理关系。编写一个查询来获得你要的数据,否则要通过迭代一个被映射的集合来获取。
  •     StatelessSession绕过任何被启用的org.hibernate.Interceptor,并且无法通过事件系统被拦截。
  •     你没有受保护的对象同一性范围。同一个查询生成两个不同的内存脱管实例。如果没有小心地实现持久化类的euqals()方法,则可能导致数据别名。
107.数据过滤和拦截
    Hibernate给动态的数据库视图提供了另一种可选的方法:运行时通过动态参数化的数据过滤器(data filter)。
    Hibernate提供org.hibernate.Interceptor接口,允许你钩如Hibernate内部处理,并执行如审计日志这样的附带作用。
    Hibernate内核基于一个事件/监听器(event/listener)模型(内部最后重构的结果)。例如,如果对象必须被加载,就触发LoadEvent。Hibernate内核被实现为这些事件默认的监听器,如果你喜欢,这个系统还让你插入自己的监听器公共接口。
    (1)动态数据过滤
    动态数据过滤器用一个全局唯一的名称在映射元数据中定义。你可以在喜欢的任何XML映射文件中添加这个全局的过滤器定义,只要它是在<hibernate-mapping>元素内部:   
    <filter-def name="limitItemByUserRank">
        <filter-param name="currentUserRank" type="int"/>
    </filter-def>
    你已经定义了一个数据过滤器,并把它应用到了一个持久化类。它仍然没有过滤任何东西;它必须对特定的Session在应用程序中被启动并参数化(EntityManager不支持这个API——你必须退回到Hibernate接口来实现这项功能):
    Filter filter=session.enableFilter("limitItemByUserRank");
    filter.setParameter("currentUserRank",loggedInUser.getRanking());
    通过标识符检索无法通过动态的数据过滤器限制。
    (2)动态数据过滤的用例
    Hibernate的动态过滤器在许多情况下都很有用。唯一受限于你的想象力和使用SQL表达式的能力。典型的用例如下:
  •     安全性限制(security limit)——常见的问题是对被给定的一些任意的安全性相关条件的数据访问的限制。这可以使一个用户的等级、使用和必须属于一个特定的组,或者是用户已经被分配到的一个角色。
  •     区域数据(regional data)——数据经常通过一个区域代码进行保存(例如,一个销售团队的所有业务合同)。每个销售人员都只在覆盖他们区域的一个数据集上工作。
  •     临时数据(temporal data)——许多企业应用程序需要在数据上应用基于时间的视图。(例如,看看上周的一个数据集)。Hibernate的数据过滤器可以提供帮助你实现这种功能的基本的临时限制。
 108.拦截Hibernate事件
    审计日志经常利用数据库触发器处理。另一方面,应用程序承担责任有时更好,尤其当不同的数据库之间需要可移植性的时候。
    实现审计日志需要几个元素。第一,必须给你相对其启用审计日志的持久化类做上标记。第二,定义什么信息应该被记入日志,例如用户、日期、时间和修改的类型。第三,用自动创建审计轨迹的org.hibernate.Interceptor把所有的东西连在一起。
    另一种启用拦截器的方法是,在创建SessionFactory之前,通过setInterceptor()在Configuration上全局地设置它。然而,在Configuration中设置的并且所有Session都可以启用的任何拦截器都必须被线程安全地实现!
    从Interceptor回调中调用原始的Hibernate Session是非法的。Session在拦截器调用期间处于易碎状态。在其他对象的保存期间,无法save()新对象!避免这个问题的一个好技巧是,只为保存单个AuditLogRecord对象打开新的Session。从原始的Session中复用JDBC连接。
    Session只是持久化对象(持久化上下文)的高速缓存,以及使这个高速缓存与数据库同步的一系列SQL操作。
109.本质上而言,Session接口的所有方法都与一个事件关联。load()方法触发LoadEvent,并且这个事件默认通过DefaultLoadEventListener处理。
    监听器实际上应该被认为是单例(singleton),意味着它们在请求之间被共享,因而不应该把任何事务相关的状态保存为实例变量。对于原生Hibernate中所有事件和监听器接口的一个清单,请见org.hibernate.event包的API Javadoc。一个监听器实现也可以实现多个事件——监听器接口。
    如果没有扩展DefaultLoadEventListener,则必须把内建的DefaultLoadEventListener指定为堆栈中的第一个监听器,都则要在Hibernate中禁用加载!
    声明注册的监听器无法共享实例。如果同一个类名被用在多个<listener/>元素中,则每个引用都会产生该类的一个单独实例。如果需要在监听器类型之间共享监听器实例的能力,就必须使用编程式的注册方法。
    EJB3.0实体监听器是拦截实体回调事件(例如实体实例的加载和保存)的类。这类似于原生的Hibernate拦截器。可以编写定制监听器,并通过注解或者XML部署描述符中的一个绑定,把它们附加到实体。
    实体监听器不实现任何特定的接口;它需要一个无参构造器,你把回调注解应用到特定事件中需要被通知的任何方法;可以在单个方法中合并几个回调。不允许在几个方法中重复相同的回调。
    也可以为整个层次结构把监听器应用到超类,并在orm.xml配置文件中定义默认的监听器。最后,可以通过@ExcludeSuperclassListeners和@ExcludeDefaultListeners注解,给特定的实体排除超类监听器或者默认的监听器。
    所有的毁掉方法都可以有任意的可见性,必须返回void,并且不允许抛出任何checked exception。如果爆出unchecked exception,并且有一个JTA事务正在处理,这个事务就会被回滚。
    JPA事件回调和注解
    回调注解                                                                                            描述
@PostLoad :在实体实例通过find()或者getReference()加载之后,或者在执行Java Persistence查询时触发。也在调用refresh()方法之后被调用。
@PrePersist,@PostPersist:当persist()在实体中调用时,且在数据库插入之后,立即发生
@PreUpdate,@PostUpdate:在持久化上下文与数据库同步之前和之后执行——也就是说,在清除之前和之后。只在实体状态需要同步时才触发(例如,由于被认为是脏的)
@PreRemove,@PostRemove 当调用remove()时,或者当通过级联移除实体实例时,并且是在数据库删除之后触发。
110.对象获取选项
Hibernate提供了下列方法从数据库中获取对象:
(1)导航对象图,从一个已经加载的对象开始,通过如aUser.getAddress().getCity()等这样的属性访问方法访问被关联的对象。如果持久化上下文仍然是打开的,当调用访问方法时,Hibernate就会自动加载(和预加载)对象图中的节点。
(2)通过标识符获取,当一个对象的唯一标识符值已知时,这是最方便的方法。
(3)HQL(Hibernate Query Language,Hibernate查询语言),它是一种完全面对对象的查询语言。Java QL(Java Persistence query language,JPA持久化查询语言)是HQL的一个标准子集。
(4)Hibernate Criteria接口,它提供了一种类型安全和面向对象的方式来执行查询,而不需要进行字符串操作。这种机制包括基于示例对象的查询。
(5)原生的SQL查询,包括存储过程的调用(在这里Hibernate仍然吧JDBC的结果集映射到持久化对象图中)。

你可能感兴趣的:(Hibernate)