Spring Web Flow是一个创新的Java™Web框架,它扩展了Spring MVC技术。 使用Spring Web Flow进行应用程序开发是围绕用例定义的,这些用例被定义为Web流。 根据Web流程组织开发工作区将带来更有意义的上下文开发体验。 另外,Spring Web Flow对JPA / Hibernate持久性的支持是其最重要的服务器端贡献之一。
尽管SpringSource和Spring Web Flow项目团队对Spring Web Flow进行了充分的文档记录,但对其持久性支持,尤其是其流管理的持久性机制知之甚少。 本文是对Spring Web Flow 2中Java持久性编程的深入讨论,重点讨论了流管理的持久性及其基本组成部分-流范围的持久性上下文。
在对Spring Web Flow持久性概念进行了概述之后,我将介绍一些用例,这些用例演示了处理原子和非原子Web流中的只读和读/写事务的策略。 在每种情况下,我都会解释首选交易处理策略的概念基础,并说明其缺点。 本文以我自己的指导原则作为结束,该指导原则是在Spring Web Flow 2中有效,安全地管理事务的。
本文面向熟悉Spring Web Flow 2及其基于延续的体系结构的经验丰富的Java开发人员。 用例和示例应用程序代码将特别有益于已经在其Spring Web Flow应用程序中使用JPA / Hibernate的开发人员。
JPA / Hibernate中的持久性挑战
在典型的Web应用程序中,需要两个主要步骤来处理用户请求:动作处理和视图呈现。 应用程序的主要业务逻辑驻留在操作处理中。 随后进行的视图渲染将数据馈送到视图模板中以生成演示文稿。
在JPA / Hibernate中,数据(更具体地说是实体关系)可能会急于加载或延迟加载为代理对象。 如果在视图呈现阶段已经关闭了持久上下文对象(JPA EntityManager
或Hibernate Session
),则实体将分离。 任何访问分离实体上的卸载关系的尝试都将导致LazyInitializationException
。
View中的Open Session模式(请参阅参考资料 )试图解决LazyInitializationException
问题。 当将View中的Open Session实现为过滤器或拦截器时,持久性上下文对象在视图渲染期间保持打开状态。 导航到持久实体上的卸载关系将触发其他数据库查询以按需获取关系。
“在视图中打开会话”模式的缺点是,持久性上下文对象的作用域实际上是用户请求。 因此,存储在Servlet作用域中而不是当前请求的实体总是分离的。 分离的实体需要合并/重新附加/重新加载操作才能与当前持久性上下文关联。
Spring Web Flow采用了另一种方法来通过流管理的持久性,更具体地说是流范围的持久性上下文对象来节省分离实体状态麻烦。
流管理的持久性
在Spring Web Flow的应用开发基于Web的流量 ,这通常代表一个唯一的用例的概念。 在许多情况下,整个Web流中的数据更改都必须是原子的 ,这意味着在流的不同阶段进行的更改要么整体保存到后端数据库,要么完全取消,而数据库中没有任何痕迹。
Spring Web Flow通过流管理的持久性机制促进了事务性原子Web流中的JPA / Hibernate编程。 流管理的持久性在概念上与Hibernate / Seam对话相同(请参阅参考资料 ),在Web流程(或Seam中的“页面流”)中进行的数据更改与脏实体缓存在同一流范围的持久性上下文对象中。 。 直到流程结束时才触发SQL插入/更新/删除语句,当所有更改都被刷新并一次提交给数据库时。 (请注意,“刷新”和“提交”是不同的概念:前者触发一系列SQL插入/更新/删除语句,以使脏实体与其对应的数据库值同步,而后者仅提交数据库事务。)
流管理持久性中的OptimisticLockingFailureException
乐观锁定是一种非常有效的并发控制方法,可确保数据完整性而无需在数据库中放置任何物理锁定。 尽管未强制执行,但在流管理的持久性中强烈建议使用乐观锁定。
持久性上下文在刷新时检查实体版本,如果检测到并发修改,则抛出OptimisticLockingFailureException
(在Hibernate中为StaleObjectException
)。 实体在内存中生存的时间越长,其相应的数据库值被其他进程修改的可能性就越大。
如前所述,在“视图中打开会话”模式中,实体的持久状态取决于用户请求。 实体分离后,通常在后续用户请求中需要合并/重新附加/重新加载操作以恢复该实体的持久状态,因此,该实体及其对应的数据库值已同步。
实体在流管理的持久性中跨多个用户请求保持其持久状态。 用户请求之间不要求进行数据库同步,因此,更有可能遇到OptimisticLockingFailureException
。 诀窍是像对待任何已检查的业务异常一样,优雅地处理OptimisticLockingFailureException
。 (即使OptimisticLockingFailureException
是运行时异常会回滚数据库事务,这也是正确的。)常见的策略是为用户提供机会合并其更改或使用非过时的数据重新启动流。
流范围的持久性上下文
Web流被声明为XML格式的流定义文件。 当启动带有<persistence-context/>
标记的Web流时,将创建一个新的persistence-context对象并将其绑定到流范围。 等待用户请求时,该对象与基础JDBC连接断开连接;服务用户请求时,该对象重新连接。 在整个流程过程中,将重复使用同一持久性上下文对象,从而消除了分离实体状态和相应的LazyInitializationException
。
持久性上下文还绑定到当前请求线程,并以两种不同的方式向开发人员公开:作为隐式变量persistenceContext
或通过JPA @PersistenceContext
注释注入到任何Spring bean中。
隐式变量可直接在流定义XML文件中使用-例如:
<evaluate expression="persistenceContext.persist(transientEntityInstance)"/>
注入的JPA实体管理器可以在Spring组件中的任何地方引用,例如在DAO,服务bean或Web层bean中。
持久性上下文的类型:事务或扩展
@PersistenceContext
批注具有一个可选的属性type
,默认为PersistenceContextType.TRANSACTION
(即,与事务绑定的持久性上下文)。 使用流范围的持久性上下文进行编程时,必须使用此默认设置。 在那种情况下,注入的事务绑定的持久性上下文对象只是一个共享代理,它透明地委派给流范围的实际线程绑定的持久性上下文。
选择另一个选项PersistenceContextType.EXTENDED
,将导致所谓的“扩展实体管理器”,它不是线程安全的,因此不能在并发访问的组件(例如单例Spring Bean)中使用。 使用扩展的实体管理器作为流作用域的持久性上下文将导致应用程序中不可预测的数据库/事务行为,因此请避免使用它。
有趣的是,Seam对话通常是通过将扩展实体管理器注入到有状态会话Bean(EJB)中来实现的。 这是Spring Web Flow的流管理持久性和Seam对话之间的明显区别。
流范围的持久性上下文对象可以与@Transactional
批注结合使用,以微调流的持久性特征。
交易语义
@Transactional
批注是Spring Core包的一部分,用于指定已批注的类或方法的事务语义。 根据Spring开发团队的说法,@ @Transactional
比接口更好地应用于具体类。 默认事务语义是:
@Transactional(readOnly=false,propagation=PROPAGATION_REQUIRED,
isolation=ISOLATION_DEFAULT,timeout=TIMEOUT_DEFAULT)
readOnly :通过指定@Transactional(readOnly=false)
设置读/写事务将使持久性上下文的FlushMode
为AUTO
。 应用@Transactional(readOnly=true)
将使基础Hibernate会话的FlushMode
成为MANUAL
。
JPA 1.0不支持MANUAL
刷新也不支持只读事务,因此@Transactional(readOnly=true)
仅在基础JPA提供程序(如Hibernate)支持只读数据库事务时才有意义。 此外,Hibernate将此设置用作针对某些数据库类型的数据库提示,以优化查询性能。
传播 : propagation
属性通过挂起/恢复封闭的事务或根本没有事务来确定当前方法是在继承的事务下运行,还是在新事务中运行。
隔离 :JPA 1.0不支持自定义隔离级别,因此开发人员需要在数据库端指定默认的事务隔离级别。 Read-Committed
是乐观锁定起作用所需的最低级别。
timeout : timeout
属性指定事务在timeout
之前可以运行多长时间(并由基础事务基础结构自动回滚)。
rollbackFor,rollbackForClassname,noRollbackFor,noRollbackForClassname :作为一般规则,事务总是在代表系统错误的RuntimeException
上回滚,并在具有预定义业务含义的Checked Exception
上提交。 通过这四个回滚属性可以自定义默认语义。
Spring Core强大的事务基础结构在大多数实际开发场景中使事务管理更加容易。 在以下各节中,我们将看到Spring Web Flow如何将Spring事务基础结构与它自己的流范围的持久性上下文对象结合使用,以处理各种Web流中的持久性编程,其中包括一些演示流管理持久性的局限性。
原子流
流管理的持久性旨在解决从事务角度来看被视为原子的Spring Web Flow用例。 例如,假设您有一个在线银行系统,该系统允许用户将钱从支票帐户转移到储蓄帐户或要创建的CD帐户。 交易必须分几步进行:
- 用户选择要转账的支票账户。
- 系统显示帐户余额。
- 用户输入要转账的金额。
- 用户选择一个储蓄或CD帐户作为目标。
- 系统显示交易摘要以供审核。
- 用户决定提交或取消交易。
由于明显的并发要求,您首先要对实体类启用乐观锁定。 为此,您可以使用JPA @Version
批注或Hibernate专有的OptimisticLockType.ALL
属性。 然后,您可以将整个用例映射到标记有Spring Web Flow的<persistence-context/>
标记的单个Web流中。
Web流中的非事务数据访问
在Spring Web Flow中,默认情况下,所有数据访问都是非事务性的。 对于非事务性数据访问,Hibernate将基础数据库的auto-commit
模式设置为true
,以便每个SQL语句立即以其自身的“短事务”,提交或回滚执行。 从应用程序的角度来看,数据库短事务等效于根本没有事务。 更重要的是,Hibernate禁用了默认的FlushMode.AUTO
来进行非事务性操作。 它有效地用作FlushMode.MANUAL
。
禁用FlushMode.AUTO
对于流管理的持久性至关重要。 视图呈现阶段中的实体惰性读取也以非事务方式执行。 如果在渲染不同的视图期间发生刷新,则将没有机会在流程结束时完成延迟的刷新。 本质上, auto-commit
模式下的非事务性读取等效于隔离级别Read-Committed
事务中的Read-Committed
。 同样,刷新不会发生在非事务性写操作中。
在上述用例中,每个用户操作都可以在数据库事务之外执行,而无需指定@Transactional
注释或XML配置的事务顾问。 流范围的持久性上下文对象将在流中加载的数据作为持久实体进行管理,并将数据更改作为实体的脏状态进行缓存。
如果用户在流程结束时通过<end-state commit="true"/>
确认了汇款交易,则Spring Web Flow运行时将在读/写数据库交易中隐式调用entityManager.flush()
。 然后它将提交事务,解除持久化上下文的绑定,然后关闭它。 如果用户选择通过<end-state commit="false"/>
取消事务,则在关闭流作用域的持久性上下文后,所有缓存的数据更改都将从内存中丢弃。
流管理持久性的这种方法与JPA 1.0解释对话处理的方式完全匹配。 JpaFlowExecutionListener
类是使之实现的基础Spring Web Flow组件。 除了非事务性数据访问方法以实现流管理的持久性外,替代方法是使用只读事务。
Web流中的只读事务
在某些情况下,您可能更喜欢只读事务而不是非事务性事务。 如果查看Spring Web Flow版本中的示例“酒店预订”应用程序(请参阅参考资料 ),您会注意到@Transactional(readOnly=true)
普遍用于“预订” Web流中的所有数据访问,无论操作的读取/插入/更新/删除性质。
JPA 1.0规范不支持只读事务,因此此设置仅在某些JPA提供程序中可用。 在其JPA实现中,Hibernate将基础Hibernate会话的FlushMode
设置为MANUAL
,并将auto-commit
模式设置为false
。
实际上,对流管理的持久性进行只读事务的行为就像非事务数据访问一样,因为更改的实体仅在原子Web流的<end-state commit="true"/>
通过<end-state commit="true"/>
刷新。
如果你想有一个刷新发生之前<end-state/>
你需要调用entityManager.flush()
在你的Spring bean的方法,用一个注解@Transactional
。
从Web流直接调用<evaluate expression="persistenceContext.flush()"/>
无效,因为除<end-state commit="true"/>
以外,没有任何事务绑定到任何Spring Web Flow标记。 <end-state commit="true"/>
。 您将收到以下错误消息:
"javax.persistence.TransactionRequiredException: no transaction is in progress"
我们将在本文后面的“酒店预订”示例中介绍一下,在没有流程范围的持久性上下文的情况下 , 持久性编程面临的挑战。
有关事务传播的更多信息
我已经谈过如何根据其propagation
属性的值传播事务,但是我忽略了一个特定的用例:如果标记为@Transactional(readOnly=true, propagation=Propagation.REQUIRED)
方法将调用另一个标记为@Transactional(readOnly=false, propagation=Propagation.REQUIRED)
或相反,那么事务将如何传播?
Spring Web Flow以一种简单但优雅的方式处理此问题:它忽略第二种方法的readOnly
属性值。 简单来说,以只读方式启动的事务将保持只读状态,直到结束为止, 反之亦然 。
这对于在流管理的持久性中是不使用事务还是只读事务的问题具有有趣的含义。
只读事务的用例
通过某些JAX-WS / JAX-RS批注,可以将应用程序服务层中的Spring bean作为可重用的SOAP / REST Web服务公开。 在这些@Service
bean或它们的方法上应用@Transactional
@Service
Web服务调用与数据库事务绑定在一起。 (没有明显的理由在DAO @Repository
Bean上使用@Transactional
,除非应用程序具有折叠层体系结构,在该层没有其他地方供开发人员指定事务属性。)
再考虑一下Spring Web Flow中用于事务管理的持久性的非事务数据访问方法。 如果将@Transactional
应用于启用了Web服务的@Service
Bean,则非事务上下文可能会被覆盖。 当在方法调用链中满足服务层中指定的读/写事务时,将清除流作用域持久性上下文中的所有未决数据更改,从而导致所谓的“过早刷新”。
另一方面,在视图层Spring Bean上指定@Transactional(readOnly=true)
会覆盖服务Bean上的读/写事务设置。 事务将保持只读状态以防止过早刷新。 在为SOAP / REST Web服务通信而绕过整个Web层的情况下,应用于服务bean的@Transactional
批注可确保Web服务调用在数据库事务中运行。
在流管理的持久性中,使用只读事务而不是非事务数据访问具有很大的优势。
如前所述,流管理的持久性可解决涉及原子Web流的用例。 在本文的其余部分中,我们将集中讨论用例,这些用例要求使用非原子性的Web流,在这种情况下,不应用流管理的持久性。 请注意,在某些用例中,我们仍然能够使用流范围的持久性上下文对象。
非原子Web流
从业务流程管理(BPM)角度看,一种长期运行的流程的寿命比典型Web会话的寿命更长。 如果这样一个长时间运行的过程涉及人工任务,那么用户可以在任何时间跨度上处理该过程,并在数小时,数天甚至数月内回来以恢复该过程的执行。 显然,这样的过程也应该能够在服务器崩溃后幸免。
所有这些因素表明,每个进度之后的长时间运行状态都需要持久保存到后端数据库中。 将这种长时间运行的人工活动实现为Web流将是一个合理的技术解决方案。 该流程将在不同的Web会话中重复执行,以模拟长时间运行的流程的生命周期。
除上述情形外,某些应用程序还包含非上下文的Web页面,用户可以在这些Web页面之间任意导航。 即使没有逻辑顺序流,也没有开始或结束状态,这些网页也可以根据其业务功能分组为流。 每个用户请求期间所做的数据更改都需要保存。 这些应用程序中的持久性编程与上面讨论的长时间运行过程没有什么不同,因为事务原子性的作用范围是每个用户操作,而不是一系列用户操作-Web流。
非原子Web流的用例
在医疗保健行业中,服务提供商定期与患有慢性疾病的成员接触,以评估其健康状况和潜在风险。 卫生提供者随后向他们提供医疗和行为健康建议。 这就是所谓的案例管理 。
案件管理系统以一系列联系任务为中心。 在一项典型任务中,案例管理员通过电话与成员联系,询问评估问题,并根据响应提出适当的建议,创建引荐,记录联系结果并设置后续任务。
并发症很多。 评估问题的列表可能很长:呼叫可能由于各种原因而中断,某些任务可能会在没有记录推荐的情况下无法完成,等等。 联系人任务包含并发或异步操作是一个长期运行的过程,并且每个步骤的进度都需要保存到数据库中。 可以将联系任务模拟为单个Web流,在长时间运行的过程中可以重复输入并执行该任务。
Spring Web Flow文档未涵盖这种非原子的Web流场景。 在此用例中,是否仍然可以利用流作用域的持久性上下文对象? 答案是肯定的。
指定交易范围
我们知道,流定义文件中的<persistence-context/>
标记为我们提供了线程绑定和flowScoped
持久性上下文,它的好处是没有分离的实体并且没有 LazyInitializationException
。 因此,我们选择保留该标签。 与原子流中的流管理持久性相比,最显着的变化发生在事务范围上:原子性适用于流程的每个步骤,而不是整个流程。 通常,流程中的一个原子步骤是用户动作,该动作由Web流定义中的<transition>
标记表示。
令人失望的是,Spring Web Flow不支持其任何标签上的事务划分,包括<transition>
和<evaluate>
。 开发人员的下一个选择是从使用@Transactional
注释的Spring bean的方法启动数据库事务,然后从<evaluate>
标记调用该方法。 ( <transition>
标记不支持方法调用。)
本质上,事务的范围是流中的<evaluate>
标签。 应用@Transactional(readyOnly=false)
将使JPA / Hibernate FlushMode AUTO
,以便Hibernate确定何时刷新同一事务上下文中的数据更改。 为了方便编程和优化SQL,在这种情况下,自动刷新优于手动刷新。 请注意,在同一<transition>
下允许使用多个<evaluate>
标记,从而导致每个用户操作进行多个数据库事务。
如果每个用户操作/请求都被视为原子操作(通常是正确的),我们希望将所有数据库写操作@Transactional
一个Spring bean的单个@Transactional
方法内,以便它们与相同的事务上下文绑定并通过相同的调用<evaluate>
标签。 清单1显示了我们如何为原子请求指定事务上下文。
清单1.为原子用户操作指定事务上下文
<transition>
<evaluate expression="beanA.readAlpha()"/>
<evaluate expression="beanA.readBeta()"/>
<evaluate expression="beanB.readGamma()"/>
<evaluate expression="beanA.writeAll()"/> <!-- a single read/write transaction -->
<evaluate expression="beanB.readEta()"/>
</transition>
清单2显示了一个不典型的情况,其中同一用户请求中涉及多个读/写事务(自行提交或回滚)。 因此,用户请求变成非原子的,这在大多数开发方案中都是灾难性的。
清单2.为非原子用户动作指定事务上下文
<transition>
<evaluate expression="beanA.readAlpha()"/>
<evaluate expression="beanA.readBeta()"/>
<evaluate expression="beanB.readGamma()"/>
<evaluate expression="beanA.writeDelta()"/> <!-- read/write transaction -->
<evaluate expression="beanA.writeEpsilon()"/> <!-- read/write transaction -->
<evaluate expression="beanB.writeZeta()"/> <!-- read/write transaction -->
<evaluate expression="beanB.readEta()"/>
</transition>
我们将如何处理在同一<transition>
下其他<evaluate>
标签所引用的那些只读操作? 我们有三种选择:
- 如前所述,在没有数据库事务的情况下运行只读操作。
- 将它们标记为
@Transactional(readOnly=false)
,以使SQL查询在读/写数据库事务下执行。 在这种情况下,流作用域持久性上下文的FlushMode
始终为AUTO
。 - 用
@Transactional(readOnly=true)
标记它们。 在这种情况下,对于这些只读事务,FlushMode
变为MANUAL
,并在达到读/写事务后转换为AUTO
。
在提交读/写事务之前,JPA / Hibernate自动刷新持久性上下文中的未决更改。 为了简单起见,Hibernate团队鼓励开发人员在这种情况下对所有数据操作一致地应用读/写事务。 只需将readOnly=false
设置为适用@Transactional
。
意外的OptimisticLockingFailureException
在非原子Web流中使用流范围的持久性上下文时,您可能会遇到一些意外的OptimisticLockingFailureException
。
仍然强烈建议对非原子Web流使用乐观锁定,以保护每个用户操作的数据完整性。 当实体的@Version
字段是数据库生成的整数或时间戳记时,在执行更新操作后,需要显式查询该实体以刷新其在持久性上下文中的状态。 否则, @Version
字段将带有陈旧的值,并且在不同事务中对同一实体的后续更新将导致OptimisticLockingFailureException
。 具有讽刺意味的是,这将在没有多用户并发的情况下发生。 相反,必须在原子流中避免执行此更新后查询操作,否则将发生过早刷新。 毕竟,无论在原子Web流中将实体对象在内存中更新多少次,流结束时发生SQL刷新都只会看到实体实例的最终状态。
显然,流作用域的持久性上下文使原子和非原子Web流中的持久性编程更加流畅和简单。 没有流范围的持久性上下文对象的Web流中的编程持久性仍然是可行的,但会带来许多障碍和陷阱。
没有流范围的持久性上下文的持久性编程
在某些情况下,如酒店预订示例应用程序所示,可以在没有<persistence-context/>
标记<persistence-context/>
实现Web流。 这种方法最明显的影响是对原子Web流的影响,一旦您省略了流作用域的持久性上下文对象,就不再可以实现此功能。 我将在以下各节中讨论其他不便之处。
持久性上下文范围仅限于数据库事务
如果没有流作用域的持久性上下文,则默认情况下通过@PersistenceContext
批注注入的持久性上下文的作用域为数据库事务。 为了更好地理解这为什么会带来问题,请查看“酒店预订”示例应用程序中的以下代码片段:
清单3.酒店预订中“主流”定义的一部分
<view-state id="enterSearchCriteria">
<on-render>
<evaluate expression="bookingService.findBookings(currentUser.name)"
result="viewScope.bookings" result-type="dataModel" />
</on-render>
<transition on="cancelBooking">
<evaluate expression="bookingService.cancelBooking(bookings.selectedRow)" />
<render fragments="bookingsFragment" />
</transition>
</view-state>
如果清单3中引用的cancelBooking
方法定义如下:
清单4. cancelBooking方法
@Service("bookingService")
@Repository
public class JpaBookingService implements BookingService {
//...
@Transactional
public cancelBooking(Booking booking){
if (booking != null) {
em.remove(booking);
}
}
然后,当我们运行代码时,将得到以下错误:
Caused by: java.lang.IllegalArgumentException: Removing a detached instance
从<on-render>
标记返回的booking
实体在后续操作<transition on="cancelBooking">
。 这两种方法findBookings
和cancelBooking
相同的bookingService
豆都在不同的数据库事务执行,因此与两个不同的持久性的上下文中的对象相关联。 从一个持久性上下文管理的booking
实体与另一个持久性上下文分离。
为了避免这个问题,在清单5所示的cancelBooking
方法中,相同的booking
实体在删除前通过其主键重新加载。
清单5.固定的cancelBooking方法
@Service("bookingService")
@Repository
public class JpaBookingService implements BookingService {
//...
@Transactional
public cancelBooking(Booking booking){
booking = em.find(Booking.class, booking.getId()); // reinstate the persistent entity
if (booking != null) {
em.remove(booking);
}
}
有效地,事务作用域的持久性上下文的工作方式与OpenSessionInViewFilter
/ Interceptor
具有singleSession=false
。 这意味着同一请求中的每个事务都有自己的关联会话。 但是,这里我们失去了View的“延迟关闭模式”中的Open Session的好处。
在视图呈现期间,延迟读取将导致LazyInitializationException
,因为在每个事务完成之后,将立即关闭事务范围的持久性上下文。 可以选择实现类似于OpenSessionInViewInterceptor
/ OpenEntityManagerInViewInterceptor
功能,但是Spring Core提供的功能不能立即使用。 使用内置的流作用域持久性上下文对象要方便得多!
持久性上下文的范围仅限于每次调用
没有流作用域的持久性上下文的辅助的非事务性数据访问是最坏的情况,应尽可能避免。
在事务外部,持久性上下文的作用域是使用FlushMode AUTO
和auto-commit true
进行的每次调用。 (请记住,Hibernate禁用了非事务性数据访问的自动刷新。)换句话说,通过@PersistenceContext
注入的同一持久性上下文代理上的每个方法调用将返回不同的实体管理器实例,该实例立即打开和关闭。
本质上,实体管理器的作用范围是“短期交易”。 清单5中看到的相同代码段将为您提供以下错误消息:
java.lang.IllegalArgumentException: Removing a detached instance
以不同的流程传递实体
这是有时会导致问题的最后一种情况:当要求您以不同的流程传递实体时会发生什么?
流范围的持久性上下文对象受流范围的约束,因此,一旦从一个流传递到另一个流,实体将立即分离。 解决方案是将这些实体合并/重新附加到当前流的持久性上下文中,或者将它们的主键重新加载,这是一种类似于“在视图中打开会话”方法的策略。
结论
Spring Web Flow作为高级Web开发框架,提供了独特的功能来支持JPA / Hibernate的持久性编程和事务管理。 本文探讨了Java开发人员在编写Spring Web Flow应用程序时面临的复杂性和挑战。
通过本文中讨论的实际用例,我设计了以下“经验法则”用于在Spring Web Flow中编码事务性原子和非原子Web应用程序:
- 作为首选,请始终使用流范围的持久性上下文
- 将只读事务普遍应用于原子Web流中引用的所有方法
- 将读写事务普遍应用于非原子Web流中引用的所有方法
翻译自: https://www.ibm.com/developerworks/java/library/j-springwebflow/index.html