EJB 3.0 (JPA) 的 Persistence Context
大家在使用 EJB 3.0 的时候会注意到 EJB 3.0 中的容器管理 Persistence Context 有两种类型,一种是 Transaction,另一种是 Extended。这是一个较 Hibernate 的 Session 所没有的概念,Session 没有两种不同的类型,而且最重要的是 Session 不是容器管理的,这里的容器指的是 App Server 容器。这里暂时不谈论 Persistence Context 与 Session 之间的异同,主要谈谈两种 Persistence Context 之间的不同。学过 ORM 的同学都知道,当 Persistence Context 是打开状态的时候,Model 就处于被管理的状态中;当 Persistence Context 关闭之后,Model 就处于了 Detached 状态。
上面这些特性对于 Transaction 或 Extended 的 Persistence Context 都是一样的,不同的地方在于 Persistence Context 何时被打开关闭。由于绝大多数情况下 Persistence Context 是被容器管理的(如果你不嫌累也可以自己控制 Persistence Context),所以在 EJB 3.0 应用中看不到打开或关闭 Persistence Context 的代码(Spring + Hibernate 的应用也同样如此,Hibernate Session 的管理工作可以交给 Spring 来做)。
其实,Transaction 和 Extended Persistence Context 的不同之处也就在于容器何时打开或关闭 Persistence Context。Transaction 类型的 Persistence Context 的打开和关闭是和事务的打开和关闭是同步的。也就是说在一个事务开始之后,Persistence Context 才会开始;在事务关闭的时候,相应的 Persistence Context 也会被关闭。
Extended 类型的 Persistence Context 的打开和关闭是和 Stateful Session Bean 的生命周期同步的,是跨越事务的。也就是说,从 SFSB 的初始化开始,直到销毁,Persistence Context 都是存在的。你可以在事务之外执行写操作,但是这是并不会执行真正的数据库操作,写操作只是放入了队列,直到下一个事务,写操作才会真正地被执行。两者的不同简单说来就是 Extended Persistence Context 存在的时间更长。那为什么要有两种不同的 Persistence Context 呢?
当一个 Web 请求到来时,服务器会打开一个线程,这个线程可能会调用一个事务方法,这是一个事务便开始了,当这个请求结束时,线程关闭,事务也随之结束。由于 Transaction 类型的 Persistence Context 的生存周期是在事务范围之内的,所以一个 Web 请求的结束也意味着相应的 Persistence Context 的关闭。由于多数 Web 应用在一次 Web 请求内即可完成一个独立的操作,所以大部分情况下 Transaction 的 Persistence Context 是适用的。但是对于一些复杂的应用,一次操作需要跨越多次请求。这种情况下,如果依旧使用 Transcation 的 Persistence Context,由于每次请求结束后,相应的 Persistence Context 都被关闭,相应的 Model 也就变为 Detached 状态。如果接下来的请求仍然需要这些已经变为 Detached 状态的 Model 就需要重新 load,使用 merge() 方法来持久化。稍有不适就会产生 LazyInitializationException 和 NonUniqueObjectException。同时,这也提高了操作的复杂程度。
如果使用 Extended Persistence Context 就能解决这些问题。由于 Extended Persistence Context 的生命周期是与 SFSB 的生命周期同步的,所以只要多次请求调用的都是同一个 SFSB 中的方法,有多少次的请求,Persistence Context 总是同一个,其中的 Model 也始终是被管理的。很好地解决了 Persistence Context 在线程之间传递的问题,也不会有 LazyInitializationException 和 NonUniqueObjectException 问题的发生。
Seam-managed Persistence Context
EJB 3.0 容器管理之下的 Persistence Context 很不错,能解决很多问题,但是还是有些问题无法解决。Seam 很强大,如果有些问题 EJB 容器解决不了了,没关系,把 Persistence Context 交由 Seam 来管理就 OK 了。那 Seam 都能解决哪些 EJB 不能解决的问题呢?先考虑下面两个问题:
1. Extended Persistence Context 虽然可以跨越多个事务,但是每个事务照旧调教不误,这对于想在想让整个操作作为一次事务的话,该如何去做
2. 如果一个业务的一组请求只是调用同一个 SFSB 的话,那么 EJB 的 Extended Persistence Context 可以在线程之间传递,使 SFSB 的整个生命周期都使用同一个 Persistence Context。但如果业务需要调用不同的 SFSB 的话,如何在 SFSB 之间传递。
对于第一个问题,由于 Seam 的 JPA 实现提供者是 Hibernate,而且 Hibernate 提供了一个扩展的 FlushModeType - "Manual"。通过使用这个 FlushModeType,我们可以手工控制何时执行 flush() 操作。在 Seam 的文档中有关于这部分的介绍 《Seam-managed persistence contexts and atomic conversations 》。文档中使用了一段简单的代码展示 Seam 如何实现所谓的 "atomic conversations"(关于代码的内容我就不介绍了,大家通过我提供的链接来浏览 Seam 的文档)。通过这种方式,事务貌似是跨越了整个事务,但我认为 SFSB 中除了调用 flush() 的方法以外的其它方法不是事务的。其实也没有必要,因为这些方法并没有执行数据库操作,所以没有必要使用事务。当然,如果是乐观事物的话,使用了对性能影响也不大(这只是我的一点浅薄的理解,欢迎指出错误)。只有最后的调用了 flush() 的方法有事务的必要。
这就引发了一个令我不解的问题。请先看这篇文章《Extended Persistence Context in Stateful Session Beans 》。在这篇文章介绍了如何只使用 EJB 3.0 去解决“问题1”。文中的 SFSB 的默认事务属性是 "NOT_SUPPORTED",也就是说这个 SFSB 中的方法默认不是事务的。只有最后的调用 flush() 的方法使用了 "REQUIRED" 的事务属性去覆盖默认设置。也就是最后的方法是事务的,其它的不是。这和 Seam 所做的有区别吗?感觉没有区别。但我认为像 Gavin King 这样的大牛绝不会做无用功的,那问题就是 Seam 实现 "atomic conversations" 的内部细节到底是什么呢?欢迎大家回答这个问题。
对于第二个问题,可以通过使用 Seam-Managed Persistence Context 来解决。Seam-manged Persistence Context 需要在 components.xml 文件中进行配置,并使用 @In 注入到 Seam 的组件中。由于 Seam 是一个比较新的框架技术,所以关于 Seam 是如何使 Persistence Context 在组件中传递并没有详细的介绍。应该只是声明,然后透明地使用即可。在一个 jBPM 流程中被使用到的 Seam 组件,其中的 Persistence Context 应该是可以很容易地被传递。(本不应该使用不确定的和模糊的词语,但无奈现在关于 Seam 的文章资料还是有限,所以我也没有找到关于 Persistence Context 在组件之间调用的例子)
大家在使用 EJB 3.0 的时候会注意到 EJB 3.0 中的容器管理 Persistence Context 有两种类型,一种是 Transaction,另一种是 Extended。这是一个较 Hibernate 的 Session 所没有的概念,Session 没有两种不同的类型,而且最重要的是 Session 不是容器管理的,这里的容器指的是 App Server 容器。这里暂时不谈论 Persistence Context 与 Session 之间的异同,主要谈谈两种 Persistence Context 之间的不同。学过 ORM 的同学都知道,当 Persistence Context 是打开状态的时候,Model 就处于被管理的状态中;当 Persistence Context 关闭之后,Model 就处于了 Detached 状态。
上面这些特性对于 Transaction 或 Extended 的 Persistence Context 都是一样的,不同的地方在于 Persistence Context 何时被打开关闭。由于绝大多数情况下 Persistence Context 是被容器管理的(如果你不嫌累也可以自己控制 Persistence Context),所以在 EJB 3.0 应用中看不到打开或关闭 Persistence Context 的代码(Spring + Hibernate 的应用也同样如此,Hibernate Session 的管理工作可以交给 Spring 来做)。
其实,Transaction 和 Extended Persistence Context 的不同之处也就在于容器何时打开或关闭 Persistence Context。Transaction 类型的 Persistence Context 的打开和关闭是和事务的打开和关闭是同步的。也就是说在一个事务开始之后,Persistence Context 才会开始;在事务关闭的时候,相应的 Persistence Context 也会被关闭。
Extended 类型的 Persistence Context 的打开和关闭是和 Stateful Session Bean 的生命周期同步的,是跨越事务的。也就是说,从 SFSB 的初始化开始,直到销毁,Persistence Context 都是存在的。你可以在事务之外执行写操作,但是这是并不会执行真正的数据库操作,写操作只是放入了队列,直到下一个事务,写操作才会真正地被执行。两者的不同简单说来就是 Extended Persistence Context 存在的时间更长。那为什么要有两种不同的 Persistence Context 呢?
当一个 Web 请求到来时,服务器会打开一个线程,这个线程可能会调用一个事务方法,这是一个事务便开始了,当这个请求结束时,线程关闭,事务也随之结束。由于 Transaction 类型的 Persistence Context 的生存周期是在事务范围之内的,所以一个 Web 请求的结束也意味着相应的 Persistence Context 的关闭。由于多数 Web 应用在一次 Web 请求内即可完成一个独立的操作,所以大部分情况下 Transaction 的 Persistence Context 是适用的。但是对于一些复杂的应用,一次操作需要跨越多次请求。这种情况下,如果依旧使用 Transcation 的 Persistence Context,由于每次请求结束后,相应的 Persistence Context 都被关闭,相应的 Model 也就变为 Detached 状态。如果接下来的请求仍然需要这些已经变为 Detached 状态的 Model 就需要重新 load,使用 merge() 方法来持久化。稍有不适就会产生 LazyInitializationException 和 NonUniqueObjectException。同时,这也提高了操作的复杂程度。
如果使用 Extended Persistence Context 就能解决这些问题。由于 Extended Persistence Context 的生命周期是与 SFSB 的生命周期同步的,所以只要多次请求调用的都是同一个 SFSB 中的方法,有多少次的请求,Persistence Context 总是同一个,其中的 Model 也始终是被管理的。很好地解决了 Persistence Context 在线程之间传递的问题,也不会有 LazyInitializationException 和 NonUniqueObjectException 问题的发生。
Seam-managed Persistence Context
EJB 3.0 容器管理之下的 Persistence Context 很不错,能解决很多问题,但是还是有些问题无法解决。Seam 很强大,如果有些问题 EJB 容器解决不了了,没关系,把 Persistence Context 交由 Seam 来管理就 OK 了。那 Seam 都能解决哪些 EJB 不能解决的问题呢?先考虑下面两个问题:
1. Extended Persistence Context 虽然可以跨越多个事务,但是每个事务照旧调教不误,这对于想在想让整个操作作为一次事务的话,该如何去做
2. 如果一个业务的一组请求只是调用同一个 SFSB 的话,那么 EJB 的 Extended Persistence Context 可以在线程之间传递,使 SFSB 的整个生命周期都使用同一个 Persistence Context。但如果业务需要调用不同的 SFSB 的话,如何在 SFSB 之间传递。
对于第一个问题,由于 Seam 的 JPA 实现提供者是 Hibernate,而且 Hibernate 提供了一个扩展的 FlushModeType - "Manual"。通过使用这个 FlushModeType,我们可以手工控制何时执行 flush() 操作。在 Seam 的文档中有关于这部分的介绍 《Seam-managed persistence contexts and atomic conversations 》。文档中使用了一段简单的代码展示 Seam 如何实现所谓的 "atomic conversations"(关于代码的内容我就不介绍了,大家通过我提供的链接来浏览 Seam 的文档)。通过这种方式,事务貌似是跨越了整个事务,但我认为 SFSB 中除了调用 flush() 的方法以外的其它方法不是事务的。其实也没有必要,因为这些方法并没有执行数据库操作,所以没有必要使用事务。当然,如果是乐观事物的话,使用了对性能影响也不大(这只是我的一点浅薄的理解,欢迎指出错误)。只有最后的调用了 flush() 的方法有事务的必要。
这就引发了一个令我不解的问题。请先看这篇文章《Extended Persistence Context in Stateful Session Beans 》。在这篇文章介绍了如何只使用 EJB 3.0 去解决“问题1”。文中的 SFSB 的默认事务属性是 "NOT_SUPPORTED",也就是说这个 SFSB 中的方法默认不是事务的。只有最后的调用 flush() 的方法使用了 "REQUIRED" 的事务属性去覆盖默认设置。也就是最后的方法是事务的,其它的不是。这和 Seam 所做的有区别吗?感觉没有区别。但我认为像 Gavin King 这样的大牛绝不会做无用功的,那问题就是 Seam 实现 "atomic conversations" 的内部细节到底是什么呢?欢迎大家回答这个问题。
对于第二个问题,可以通过使用 Seam-Managed Persistence Context 来解决。Seam-manged Persistence Context 需要在 components.xml 文件中进行配置,并使用 @In 注入到 Seam 的组件中。由于 Seam 是一个比较新的框架技术,所以关于 Seam 是如何使 Persistence Context 在组件中传递并没有详细的介绍。应该只是声明,然后透明地使用即可。在一个 jBPM 流程中被使用到的 Seam 组件,其中的 Persistence Context 应该是可以很容易地被传递。(本不应该使用不确定的和模糊的词语,但无奈现在关于 Seam 的文章资料还是有限,所以我也没有找到关于 Persistence Context 在组件之间调用的例子)