作者:狂放不羁 
   网址: http://yuquan.blog.51cto.com
    >>>转载请注明出处!<<<
 
 
接着上篇“J2EE持久层持久化上下文传播总结”,这篇文章总结一下如何利用当今J2EE比较流行的持久层框架Hibernate和持久层标准JPA来实现会话。
 
在讨论此问题之前,首先要明确一个问题,什么是会话?会话简单的可以理解为跨多个request生命周期,就拿那个投修改简历的问题来说,第一个事务读取简历,用户修改,然后再通过另外一个事务去更新简历,那么读取和更新是在不同的事务中完成的,那么我们怎么实现这种用例呢?如果采用ORM框架,实现起来会轻松很多,下面就以hibernate和JPA来做一总结。

1. Hibernate实现会话:在hibernate中实现会话可以采用两种策略。

第一种就是采用detached object,在此种情况下,我们只需要用hibernate提供的reattach和merge操作将脱管对象重附或者是合并到当前的持久化上下文,hibernate会为我们检测更新并最后决定同步到数据库。
第二种就是通过ManagedSessionContext来扩展持久化上下文,使其跨越几个request生命周期。在使用ManagedSessionContext来实现扩展持久化上下文时,一定要记得设置session的flushmode为FlushMode.MANUAL.这样在每次事务提交的时候就不会同步持久化上下文。(不幸的是JPA规范中没有此选项,我们只能通过非事务型的操作来实现,但是如果采用EJB模型,那么我们就可以采用statefull session bean默认的扩展的持久化上下文)。
下面我主要说一下如何在JPA中来实现会话:

2. JPA中实现会话:在JPA中实现会话,我主要结合EJB的编程模型来做一总结,因为EJB编程模型为我们做了很多简化,免得我们自己动手实现,但是如果要在非J2EE托管环境下使用JPA,就要自己实现。

第一种策略:采用脱管对象来实现。在这之前首先要明确一个问题,那就是JPA只提供了对脱管对象的merge(合并)操作,没有提供reattach(重附操作),所以我们采用脱管对象实现会话时,在会话的最后一个request生命周期中,我们通过合并来将脱管对象合并到持久化上下文,当然detached object对象,我们一般是保存到httpsession里面,但是这样一个不好的地方就是将业务逻辑泄露到了表现层,不利于层的内聚性,所以我们可以采取EJB编程模型给我们带来的便利方式来实现,通过扩展的持久化上下文来实现,下面就来说说如何通过扩展的持久化上下文来实现会话。
第二种策略:采用statefull session bean扩展的持久化上下文来实现。因为在EJB3.0中,有状态会话bean默认采取扩展的持久化上下(extended persistence context),持久化上下文的生命周期与statefull session bean的生命周期是一致的,所以我们可以通过此扩展的持久化上下文来实现会话。为了更加清楚,我采用以下代码来描述:
@Statefull
@Remote(BussinessInterface.class)
public class ConversationBean implements BusinessInterface{
 
@PersistenceContext(type = PersistenceContextType.EXTENDED)
EntityManager em ;
 
public OperationResult firstOperation(...){
//此方法实现会话的第一个请求
}
 
//省略会话的中间阶段的请求方法,我只用一个来描述
public OperationResult middleOperation(..){
//此方法可以实现会话的一些中间操作,比如查询等
}
 
..........
 
//会话的最后一个请求
public OperationResult lastOperation(...){
 
 
}
 
 
}
但是此种情况下实现会话需要注意一个问题,因为EJB3.0规范没有像hibernate那样的人工刷新持久化上下文的选项,所以此时要想会话的中间阶段不刷新持久化上下文,为了让会话的中间阶段不刷新持久化上下文(同步持久化上下文到数据库),那么或者采用hibernate扩展或者采用非事务型的EJB组件方法调用(因为JPA规范只有两种持久化上下文的刷新模式,一种是AUTO,它是默认的刷新选项,第二种是commit,第一种要求在执行查询和事务提交的时候都刷新持久化上下文)。分两种情况来描述:
采用Hibernate扩展: 此种情况下,我们需要增加以下注释:
........
@PersistenceContext(type=PersistenceContextType.EXTENDE,
  propertites = @PersistenceProperty(name="org.hibernate.flushMode",   
  value="MANUAL")
EntityManager em;
 
......
 
以上的注释也可以采用等价的XML部署描述来替换。
通过以上的注释,当我们的客户端调用会话中间操作时,当前与ConversationBean绑定的扩展的持久化上下文就不会刷新(因为默认会在middleOperation()方法调用的时候启动事务,结束后提交事务,而如果按照JPA标准的话,flushMode无论是AUTO,还是COMMIT,都会刷新持久化上下文,并且如果我们在中间操作进行查询操作,也会触发刷新持久化上下文的操作),当然因为当前的持久化上下文是人工刷新的,那么我们就需要在会话的最后一个操作中,本例中,也就是lastOperation中手动刷新数据库,代码如下:
 
public OperationResult lastOperation(...){
 
em.flush();
em.commit();//注意此时em是可选的,因为默认在方法调用后会自动提交事务。
}
此时我们还可以将一个会话通过面向对象的设计来进行分解,比如我们可能还需要其它的EJB来实现会话,那么我们可以将其通过@EJB注释让容器帮我们注入,这样以来不同的EJB之间的传播会通过当前的系统事务来传播持久化上下文(具体请参考上篇持久化上下文传播)
采用目前JPA规范建议方法:非事务型的EJB方法调用。我们可以修改ConversationBean的代码如下:
@Statefull
@Remote(BussinessInterface.class)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORT)
public class ConversationBean implements BusinessInterface{
 
@PersistenceContext(type = PersistenceContextType.EXTENDED)
EntityManager em ;
 
public OperationResult firstOperation(...){
//此方法实现会话的第一个请求
}
 
//省略会话的中间阶段的请求方法,我只用一个来描述
public OperationResult middleOperation(..){
//此方法可以实现会话的一些中间操作,比如查询等
}
 
..........
 
//会话的最后一个请求
@Remove
@TransactionAttribute(TransactionAttributeType,REQUIRED)
public OperationResult lastOperation(...){
 
   em.flush();
   em.commit();//注意此时em是可选的,因为默认在方法调用后会自动     提交事务。
 
}
}
以上代码改变的地方就是我们将ConversationBean的事务属性改为了not support(默认情况下是REQUIRED),这样以来我们的ConverstationBean的调用就不会在事务中进行,所有的会话操作都会放在一个队列中,当我们的扩展的持久化上下文重新与事务关联的时候,当事务提交时同步到数据库。但是有个问题,如果ConversationBean还会调用的EJB bean怎么办?因为当前的bean不支持事务,那么持久化上下文是不能通过事务来传播的,比如ConversationBean会与另外一个bean进行协作,我们定义如下:
@Stateless
@Remote(AnotherBussinessInterface.class)
public class AssistBean implements AnotherBusinessInterface{
 
.........
}
 
此时我们需要增加如下代码到ConversationBean中:
.......
//通过@EJB注释,EJB容器会将实例池里的一个实例注入到当前的 ConversationBean里。
@EJB
AnotherBusinessInterface assistBean;
........
 
这样以来,我们遇到了持久化上下文传播的问题,因为此时ConversationBean不支持事务,所以扩展的持久化上下文是传播不到没有无状态会话bean里的(这个是JPA规范规定的,不能在没有事务的情况下,传播扩展的持久化上下文到stateless session bean),那么既然持久化上下文不能得到传播,那么我们同一个会话中就会有不同的持久化上下文,这样就不能保证会话的完整性。那么我们有没有办法来采取采取措施补救呢?幸亏还有哈哈,那就是我们可以将AssistBean改为有状态的会话bean,这样以来,因为扩展的持久化上下文此时通过bean实例来传播的,所以不同的有状态会话bean里会采用同一个持久化上下文。所以我们要想通过有状态会话bean的扩展的持久化上下文来实现会话,会话设计到的所有的bean必须都要是statefull,造成这样不方便的原因也就在与当前JPA标准不支持FlushMode.MANUAL,如果JPA也支持了此选项,那么我们通过扩展的持久化上下文实现会话将变得更加容易,希望以后JPA标准能加入FlushMode.MANUAL选项。
综上所述,实现会话我们可以采取两种措施,第一种就是通过detached object,这样的不好地方就是业务状态都保存到了表现层,不利于分层架构,不利于层的内聚性,所以为了将业务状态保存到业务层,那么我们可以采取扩展的持久化上下文,虽然hibernate支持flushModel.MANUAL的选项,但是要自己动手实现扩展的持久化上下文,而EJB提供了扩展的持久化上下文,但是又没有提供FlushMode.MANUAL的选项,所以如果各有个的好处,遗憾的是不能两全其美。但是当采用EJB的扩展的持久化上下文,这样不用自己动手实现,唯一不好的地方就是要么通过hiberante的扩展来关闭事务提交自动刷新持久化上下文,要么通过非事务型的EJB方法调用来关闭事务提交自动刷新持久化上下文,所以如果我们EJB容器采用了Hiberante做为JPA标准的实现,那么我们最好借助与Hiberante的扩展,以及statefull session bean的扩展的持久化上下文来实现会话。