设计Java EE应用程序--事务管理

[size=x-small]事务管理是一种简化分布式多用户企业应用程序开发的机制。同时,它也是J2EE平台提供的标准服务之一。通过在应用程序访问和更新数据中加入一系列严格的规则,事务管理保证了数据完整性。事务性的系统保证工作的单元或者完全执行,或者完全不执行。事务管理将应用程序开发者从复杂的数据访问问题中解放出来,包括通过更新,故障恢复,和多用户编程。

本章以事务的基本概念和J2EE平台对事务管理的支持出发。然后介绍Java 事务API(JTA),它是J2EE平台用来管理和协调事务的接口。最后,本章介绍了每种J2EE组件和企业信息系统支持的事务模型。

1.1 事务的概念
事务是一组工作的逻辑集合,这些工作可以是修改一些状态,执行一组操作。一个独立的事务可以包含多个数据和逻辑操作,但这些操作总是以不可分的原子单元执行,或者完全不执行。例如,将一个病人登记到卫生保健计划中可能包含以下几步:首先得到病人的授权协议书,然后校验病人的职业,从远程数据源中检查他的健康和保险历史等等。所有这些活动都可以成为一个事务的子任务,因为其中任何一个任务失败,会导致整个事务失败。

本节提供常规的和分布式事务系统的基本概念。

1.1.1 ACID事务属性
企业事务有原子性,一致性,隔离性和持久性,缩写为ACID。这些属性可以保证安全的数据共享。

原子性意味着事务被当作一个整体,当前仅当所有操作都成功后事务才完成。如果事务中的任何操作失败,事务就会失败。在上面的卫生保健例子中,只有当病人的所有需要手续都完全办理,才能登记,所以登记是原子性的。

一致性意味着事务必须将数据从一个一致的状态转换到另一个一致的状态,保证数据的语义和引用完整。例如,如果数据库中的卫生保健策略要求病人被策略覆盖,同时策略描述覆盖范围,卫生保险应用程序的每个事务必须强制这个一致性规则。虽然应用程序应该总是保证数据一致性,很多数据库提供了说明一致性和数据值约束,所以尝试违反完整性的事务将会自动失败。

隔离性意味着事务中对其他数据的修改对其他并发事务都是不可见的,直到事务提交。隔离性要求多个并发的事务必须和这些事务以某个(未指定的)顺序串行执行,得到的结果一样。在健康计划登录例子中,隔离性保证对于病人数据的更新不会在这些更新提交前全局可见。

持久性意味着提交的更新是持久的。提交后出现的故障不会导致数据丢失。持久性同时表示所有提交的事务的数据可以在系统或媒体故障后恢复。

事务的ACID保证持久化数据总是和它们的架构一致,一系列操作可以当作一组稳定的输入和工作数据,同时持久的数据可以在系统故障后恢复。

1.1.2 事务参与者
使用事务的应用程序称作事务性应用程序。在J2EE应用程序中,事务性应用程序可以由多个servlet,JSP页面和企业bean组成。资源管理器是应用程序访问的外部系统。资源管理器为特定的数据和操作提供和强制事务的ACID属性。资源管理器的包含关系数据库(她支持关系数据的持久化存储),EIS系统(管理事务,外部功能和数据)和Java消息服务(JMS)提供者(管理事务性消息发送)。

事务性应用程序通过事务资源对象访问资源管理器。例如,JDBC java.sql.Connection对象可以用来访问关系数据库。资源适配器是让应用程序服务器可以得到资源管理器API的系统库。连接器是一种资源适配器,它有和Java连接器架构一致的API,标准的连接器可以用来将EIS整合到J2EE应用程序中。

1.1.3 事务边界
事务性程序必须可以开始和结束事务,也可以决定数据的修改是保存还是丢弃。被标识的事务的边界称作事务界限。

程序通过执行begin操作开始事务。然后,程序在新开启的事务范围内读取或修改数据。当程序准备让数据的修改永久化时,它执行commit操作,让在事务激活状态中所有修改或新建的数据都持久化保存。提交操作的成功执行导致事务性资源的永久更改。如果提交操作失败(例如,不适当的资源或违反数据一致性),资源管理器执行回滚,丢弃事务开始以来对数据的任何修改。应用程序可以在活动事务中显式的请求回滚。

1.1.4 分布式事务
分布式企业信息系统通常需要访问和更新多个事务性资源,用以完成一些业务目标。考虑一个旅行社应用程序。成功创建一个确认和购买机票的商务旅程,需要用户认证,信用卡处理,机票预定,以及旅程的本地创建。这种事务,涉及独立的,协作的事务性系统,被称作分布式事务。

正因为延迟性,一个或多个资源管理器的潜在故障以及协作的问题,所以分布式事务比非分布式事务更加复杂。在网络中,一个失败的事务很难区分是不是仅仅因为响应速度慢。资源管理器不知道双方自己不能协调事务。一个事务性的应用程序可以自己处理多个分布式资源的回滚和提交,但这样做只会增减程序的复杂性,增加不能重用的逻辑。这种问题最常用的解决方案就是在设计中引入称为资源管理器的第三方参与。事务管理器作为应用程序和它使用的多种资源额中间者。图8.1展示了分布式事务中的三种参与者:事务性应用程序,资源管理器和事务管理器,它协调多个资源管理器的事务,当应用层需跨越多个资源时,为应用程序提供ACID的事务特性。很多情况下,资源管理器使用X/Open XA协议和多个资源管理器通信。在J2EE平台中,XA协议被JTA XAResource接口封装。

在分布式应用程序中,事务管理器管理维护每个事务(它有全局唯一的ID),应用程序线程和资源管理器的连接之间的关系。例如,一个事务管理器可以将一个应用程序的线程和一个事务ID关联,SQL连接更新表,JMS提供者等待发送消息,资源适配器或连接器执行外部业务函数。事务上下文是事务和应用程序组件或资源管理器的关联。事务上下文从一个组件到另一个组件的传递,或者从组件到资源管理器的传递,称为事务上下文传播。






1.1.5 两阶段提交协议
资源管理器不知道别人能否和自己在分布式事务中直接协作,想法,事务管理器控制事务,告诉每个资源管理器是否和何时提交或回滚,这是基于事务的全局状态进行的。事务管理器使用两阶段提交协议在资源管理器之间协调事务。两阶段提交协议跨越多种资源,提供事务的ACID属性。

在两阶段提交的第一个阶段,资源管理器告诉每个资源,准备进行提交,即,执行提交的所有操作,同时准备将修改持久化或undo所有修改。每个资源管理器都应答,表示准备工作是否成功进行。

在第二阶段,如果所有准备工作都成功,事务管理器告诉所有资源管理器提交成功,否则,它告诉所有资源管理器回滚,表示应用程序的事务失败。

一个特殊的资源管理器可能同时参与到多个分布式事务中。ACID属性应用于参与某个分布式事务的所有资源管理器中,同时在某个资源管理器中等待事务。

1.2 J2EE平台事务
企业应用程序需要安全的,可信的,可恢复的数据访问,所以,对于事务的支持是J2EE架构的基本元素。J2EE平台支持在单一事务中组合servlet和JSP页面,同时访问多个企业bean。每个组件可以获得到多个资源管理器的连接。

J2EE平台同时支持编程式的和声明式的事务界限。组件提供者可以使用Java事务API在组件代码中划分事务边界。企业bean支持声明式的事务边界,即企业bean容器自动开始和完成事务,基于组件的部署描述符中的配置信息进行。在这两种情况下,J2EE平台承担实现事务管理的责任。

J2EE事务管理对于组件和应用程序代码透明。J2EE应用程序服务器实现必要的低级事务协议,例如在事务管理器和资源管理器之间交互,事务上下文传播和分布式两阶段提交协议。J2EE平台只要求支持平的事务,即不允许任何嵌套事务。

应用程序之所以可以执行分布式事务,是因为事务管理器将事务上下文在多个资源管理器之间传播。事务管理器也可以跨越J2EE服务器边界协作传播事务。想一下零售店链。商店引用程序的事务管理器可以和同一个企业中的其他商店的事务管理器协作,允许顾客在一个地方付款,在另一个地方取货。这种事务性能力为跨企业的高级整合铺平了道路。下一节的有一些这种场景的例子,它们设计分布式事务的参与。

1.2.1 在事务中访问多种资源
从J2EE平台1.3开始,J2EE产品需要支持在一个事务中支持访问以下资源:

l         一个JDBC数据库(到同一个数据库的多个连接是支持的)

l         一个Java消息服务(JMS)提供者

l         多个企业信息系统(EIS)同资源适配器(连接器)指定XATransaction事务级别

三种资源管理器请参考8.8节。

在一个事务中访问多个JDBC数据库在J2EE 1.3中不需要支持,同时在一个事务中访问多个JMS提供者也可以不支持。一些产品提供者可以在产品中增加这些额外的,非标准的事务性能力,以增加产品价值。例如,J2EE参考实现支持在一个事务中通过XA-capable JDBC驱动访问多个JDBC数据库。

1.2.1.1.1例子:跨越多个资源管理器的事务
以下场景展示了跨越多个资源管理器的J2EE事务。在图8.2中,客户端调用企业bean X中的方法。Bean X使用JDBC连接访问数据库A。然后,企业bean X调用另一个企业bean Y中的方法,它使用JMS提供者将JMS消息发送到其他系统。然后企业bean Y调用企业bean Z的方法,使用实现了J2EE连接器架构的资源适配器从外部EIS系统中更新和返回一些数据。J2EE服务器中的事务管理器协调这三个资源管理器。服务器保证数据库被bean X更新,消息传送由bean Y进行,EIS操作被bean Z执行,同时它们要么全部提交,要么全部回滚。






应用程序组件提供者必须要编写额外的代码保证事务行为的一致性。企业bean X,Y和Z各自使用JDBC API,JMS和J2EE连接器架构访问资源。在这些场景之下,J2EE服务器的事务管理器获得的这三个系统的连接,都是事务的一部分。当事务提交时,J2EE服务器和资源管理器执行两阶段提交,保证两个系统更新的原子性。

1.2.2 事务跨越服务器
J2EE产品可以跨越多个应用程序服务器分布事务。

1.2.2.1.1例子:跨越J2EE服务器的事务
在图8.3中,客户端调用企业bean X,它更新企业信息系统A中的数据,然后调用企业bean Y,它由不同的J2EE服务器管理。企业bean Y执行企业信息系统B的读写访问。






当X调用Y时,两个服务器协作将事务上下文从X传播到Y。这种事务上下文的传播对应用程序代码是透明的。在事务提交时,两个J2EE服务器使用分布式两阶段提交协议,保证两个企业信息系统都在一个事务中更新。

1.3 J2EE事务技术
J2EE事务API和J2EE平台规范定义了J2EE架构中总体的事务行为。JTA规范定义了应用程序,应用程序服务器,资源管理器和事务管理器之间的协议。J2EE平台规范定义了J2EE事务管理和运行时环境的需求。

1.3.1.1.1Java事务API(JTA)
JTA说明了事务管理器和分布式事务参与者之间的接口,它协调:应用程序,应用程序服务器和资源管理器。JTA定义的接口允许应用程序,应用程序服务器和资源管理器参与到事务中,不用考虑它们的实现。

JTA事务是由J2EE平台管理和协调的事务。J2EE规范中要求J2EE产品需要支持JTA事务。JTA事务可以跨越多个组件和企业信息系统。事务可以自动的在组件和组件在事务中访问的企业信息系统之间传播。例如,JTA事务可以包含servlet或JSP页面访问多个企业bean,其中的一些访问一个或多个资源管理器。

JTA事务或者在代码中显式开始,或者由EJB服务器隐式开启。组件可以使用javax.transaction.UserTransaction显式的开始JTA事务当。当客户端访问的企业bean使用容器管理的事务边界时,企业bean容器隐式的开始JTA事务。

大多数J2EE应用程序组件提供者只使用JTA UserTransaction接口,可以选择只使用bean管理事务还是只使用容器管理事务。应用程序组件提供者使用JTA UserTransaction接口在组件中划分JTA事务边界。JTA TransactionManager 和 XAResource接口都是J2EE服务器和企业信息系统资源管理器之间的低级的API,不应该被应用程序使用。JTA事务的主要好处是将多个组件和企业信息系统的访问组装到一个事务中进行,而且只需要少量的编码。J2EE平台将事务在多个组件和企业信息系统之间传播,而不需要编程。企业bean使用容器管理的事务边界不需要编程式的开始或提交事务,因为EJB容器自动处理边界。

当访问企业信息系统时,建议使用JTA事务。

1.4 客户层事务
J2EE平台不需要支持applet和应用程序客户端的事务,即使它们开起来象分布式事务,J2EE产品可以将这个功能作为附加值。所以,applet和应用程序客户端是否可以直接访问UserTransaction,依赖于容器的功能。为了保证可移植性,applet和应用程序客户端应该将事务代理给企业bean,或者间接的由web层进行。

1.5 Web层事务指导
在两层应用程序中servlet和JSP页面可以在JTA事务的作用域中访问企业信息系统。Servlet和JSP页面只支持编程式的事务边界。Servlet和JSP页面可以使用JNDI查询UserTransaction对象(使用标准的名称java:comp/UserTransaction),然后使用UserTransaction接口分界事务。

代码8.1展示了在servlet中使用JTA UserTransaction接口分界事务:



Context ic = new InitialContext();

UserTransaction ut =(UserTransaction) ic.lookup("java:comp/UserTransaction");

ut.begin();

// access resources transactionally here

ut.commit();


Code Example 8.1 使用JTA事务的web组件



调用UserTransaction.begin会在关联的调用线程中增加新的事务上下文。以后访问事务性资源,例如JDBC连接或资源适配器连接,会隐式的将这些资源加入事务中。对UserTransaction.commit的调用提交事务,如果需要,透明的使用两阶段提交协议。

Servlet或JSP可以只在自己的service方法中开始事务。由servlet或JSP页面开始的事务,必须在service方法返回前结束;换句话说,事务不会跨越web请求。如果服务器返回时还有未决的事务(即,已经开始了,但没有提交或回滚),容器放弃事务并且回滚所有数据更新。Servlet过滤器和web应用程序事件监听器不支持JTA事务。

1.5.1.1.1Web层事务指导
在多层环境中,数据表示和用户交互总是servlet和JSP页面的责任。数据展示和用户交互通常不是事务性操作。因为事务倾向于和业务逻辑,数据访问以及其他事务性工作关联,应该被事务性企业bean处理,而不是由web层的JTA处理。

在不使用企业bean或需要使用web层事务的设计中,可以应用以下指导。JTA事务,线程,和事务性资源(例如,JDBC连接)有一些复杂的和微妙的交互。Web组件应该遵循J2EE规范事务管理章节的指导:

l         JTA事务必须在调用service方法的线程中开始和结束。如果web组件以任何目的启动了额外的线程,这些线程不能尝试启动JTA事务。这些额外的线程不会和JTA事务关联。

l         在service方法线程之外请求和释放的事务性资源,例如JDBC连接,不应该在线程之间共享。

l         事务性资源对象不应该保存在静态字段中。

l         实现SingleThreadModel的web组件,可以将事务性资源保存在类实例字段中。根据定义,在任何时候,只有一个线程可以访问实现SingleThreadModel 的web组件实例,因此,这个实例可以看作引用的资源不会被其他线程共享。

l         没有实现SingleThreadModel的web组件,不能在类的实例字段中保存事务性资源。这些组件的事务性资源的获取和释放需要在service方法调用的同时进行。

1.6 企业JavaBean层事务
企业bean提供两种类型的事务边界:bean管理的和容器管理的。在容器管理的事务边界中,有六种事务属性可以和企业bea方法关联,它们是:Required, RequiresNew, NotSupported, Supports,Mandatory和Never。应用程序组件提供者或组装者在企业bean的部署描述符中说明企业bean方法的事务边界和事务属性的类型。

本节讨论事务的类型和容器管理事务的,同时为如何选择提供一些指导。

1.6.1 Bean管理的事务边界
在bean管理的事务边界中,企业bean使用javax.transaction.UserTransaction接口显式的分界事务界限。会话bean和消息驱动bean可以选择使用bean管理的事务边界,实体bean必须总是使用容器管理的事务边界。

代码8.2展示了使用JTA UserTransaction接口,在使用bean管理事务边界的企业bean中划分事务边界。



UserTransaction ut = ejbContext.getUserTransaction();

ut.begin();

// perform transactional work here

ut.commit();


Code Example 8.2 使用JTA事务的企业bean



在EJB层中UserTransaction接口的使用方法和web层中一样,只是获得引用是通过调用EJBContext.getUserTransactio,而不是进行JNDI查询。正如8.5节中描述,如果资源管理器是活动的,第一次在启动事务的线程中被访问时,会隐式的加入事务中。Web组件不需要显式的划分资源管理器的事务。

1.6.2 容器管理的事务边界
EJB容器为企业bean管理的事务边界,使用容器管理的事务边界。企业bean方法的事务属性,决定了方法的事务性语义,定义了方法调用时,EJB容器必须提供的行为。和企业bean方法关联的事务属性在企业bean的部署描述符中定义。例如,如果方法的事务属性为RequiresNew,每次调用这个方法时,容器开始一个新的JTA事务,然后尝试在方法返回之前提交。同样的事务属性可以为企业bean中所有方法指定,或者可以为每个bean方法指定不同的属性。事务属性的详细信息请参考8.6.3节。

即使在容器管理的事务边界中,企业bean也可以对事务进行一些控制。例如,企业bean可以选择回滚容器管理的事务,通过在SessionContext, EntityContext 和 MessageDrivenContext对象上调用setRollbackOnly方法实现。

使用容器管理的事务边界,有以下好处:

l         企业bean的事务行为可以通过声明而不是编程。这样应用程序组件提供者不用在组件代码中编写事务边界。

l         因为容器自动管理事务边界,可以产生更少的错误。

l         可以更容易的将多个企业bean执行的某个操作和指定的事务行为关联。了解应用程序的应用程序组装者可以在部署描述符中修改事务属性,不需要修改代码。

1.6.3 事务属性
事务属性是关联使用容器管理事务边界的企业bean方法的值。企业bean方法的事务属性在bean的部署描述符中定义,通常由应用程序组件提供者或应用程序组装者提供。事务属性控制EJB容器如何分界企业bean方法的事务。大多数情况下,企业bean中的所有方法有同样的事务属性。为了优化,不同的方法可能有不同的属性。例如,企业bean中有一些方法不需要事务性。

事务属性必须在会话bean组件接口的方法,实体bean的组件和home接口方法中定义。

1.6.3.1.1Required
如果事务属性是Required,容器保证企业bean的方法总是在JTA会话中被调用。如果调用的客户端关联一个JTA事务,企业bean方法将会在同样的事务上下文中调用。然而,如果客户端不需要关联事务,容器将会自动开始事务,并且尝试在方法完成时提交事务。

1.6.3.1.2RequiresNew
如果事务属性为RequiresNew,容器在调用企业bean方法之前总是开始一个新事务,在方法返回时提交事务。如果调用的客户端关联事务上下文,容器在开始新的事务之前,挂起当前线程的事务上下文。当方法和事务结束时,容器恢复挂起的事务。

1.6.3.1.3NotSupported
如果事务属性是NotSupported,调用客户端的事务上下文不会传播到企业bean。如果调用客户端有事务上下文,容器在企业bean方法调用前挂起客户端的关联的事务。在方法结束时,容器恢复被挂起的事务关联。

1.6.3.1.4Supports
如果事务属性是Supports,客户端关联事务上下文,上下文会被传播到企业bean方法中,和Required中的方式一样。如果客户端没有关联事务上下文,容器行为和NotSupported类似。事务上下文不传播到企业bean方法。

1.6.3.1.5Mandatory
事务属性Mandatory要求容器bean在客户端的事务上下文中调用bean方法。如果客户端在调用方法时没有关联事务,如果客户端是远程客户端容器抛出javax.transaction.TransactionRequiredException,如果客户端是本地客户端,抛出javax.ejb.TransactionRequiredLocalException。如果调用的客户端有事务上下文,容器把它当作Required对待。

1.6.3.1.6Never
事务属性Never要求企业bean方法显式的,不在事务上下文中被调用。如果客户端在事务上下文中调用,如果客户端是远程客户端,容器抛出java.rmi.RemoteException,如果客户端是本地客户端,容器抛出javax.ejb.EJBException。如果客户端没有和任何事务关联,容器不初始化事务,直接调用方法。

1.6.4 企业bean层事务指导
和之前提到的一样,管理事务的推荐方法是通过容器管理边界。J2EE平台提供的声明式的事务管理,有一个很大的好处,就是将应用程序组件提供者从管理事务的负担中解放出来。同时,应用程序的事务属性的修改,可以不修改代码而是切换事务属性,让组件在更多的上下文中实用。事务边界应该被了解应用程序的人小心的选择。Bean管理的事务边界只是为高级用户提供,他们需要应用程序事务行为细粒度的控制。

1.6.4.1 事务属性指导
大多数企业bean执行事务性工作(例如,访问JDBC数据库)。事务属性的默认值是Required。实用这个属性,保证企业bean的方法在JTA事务中调用。同时,事务属性为Required的企业bean,可以容易的在一个JTA事务的作用域中执行。

消息驱动bean只能使用Required和NotSupported事务属性。使用EJB 2.0容器管理持久化的实体bean,对于大多数组件和home接口方法应该只使用Required,RequiresNew或Mandatory事务属性。

当bean方法需要无条件提交,不管是否已经在事务中时,应该使用RequiresNew事务属性。例如,bean方法要记录日志时,需要调用有RequiresNew事务属性的方法,这样日志记录可以在客户端调用的事务回滚时依然记录。当资源管理器负责的事务不被J2EE产品支持时,使用NotSupported事务属性。例如,如果bean的方法调用一个没有整合到J2EE服务器中的企业信息计划系统操作时,服务器不能控制系统的事务。这种情况下,bean的事务属性应该设置为NotSupported,清楚的表示企业信息计划系统不能在JTA事务中访问。

不推荐使用事务属性Supports。使用这个属性的企业bean的事务性行为,依赖于调用者是否关联事务上下文,可能会违反事务的ACID规则。

当可以确定调用客户端的事务上下文时,可以使用事务属性Mandatory和Never。这些属性让组件在应用程序中更难使用,因为他们限制了调用客户端的上下文。

1.6.4.2 容器管理持久化的事务属性指导
和之前提到的一样,使用EJB 2.0容器管理持久护的企业bean,只能在大多数业务方法和home接口中的方法中,使用Required,RequiresNew或Mandatory这些事务属性。因为访问实体bean的容器管理持久化(CMP)和容器管理关系(CMR)字段,需要事务,实体bean的CMP和CMR字段的所有get和set方法都应该使用Mandatory事务属性。为CMR字段的get和set方法使用RequiresNew事务属性是不推荐的,因为在不同的事务上下文中,遍历相同集合对象对应的CMR字段是非法的。

1.7 EIS层事务
大多数企业信息系统支持一些格式的事务。例如,一个典型的JDBC数据库允许在一个原子事务中进行多个SQL语句的更新。

组件访问企业信息系统时,应该总是在事务的作用域中,保证底层数据的完整性和一致性。这些系统可以在JTA事务或资源管理器本地事务中访问。

1.7.1 JTA事务
当企业信息系统在JTA事务作用域内访问时,在系统中进行的更新操作,将依赖于JTA事务的结果提交或回滚。一个信息系统可以打开多个连接,并且所有同这些连接的更新操作都会是原子操作,如果他们在同一个JTA事务的作用域中执行。J2EE服务器负责在服务器和企业信息系统之间协调和传播事务。

如果J2EE产品在一个事务中支持多个企业信息系统,那么J2EE应用程序可以访问和原子的更新多个企业信息系统,不需要额外的编程,就可以将所有更新操作组织在一个JTA事务中。代码8.3展示了这种用法:



InitialContext ic = new InitialContext("java:comp/env");

DataSource db1 = (DataSource) ic.lookup("OrdersDB"); // JDBC

ConnectionFactory db2 =(ConnectionFactory)

ic.lookup("InventoryEIS"); // Connector CCI

java.sql.Connection con1 = db1.getConnection();

javax.resource.cci.Connection con2 = db2.getConnection();

UserTransaction ut = ejbContext.getUserTransaction();

ut.begin();

// perform updates to OrdersDB using connection con1

// perform updates to InventoryEIS using connection con2

ut.commit();


Code Example 8.3 访问多个事务性资源



1.7.2 资源管理器本地事务
资源管理器本地事务(或本地事务),是特定于某种企业信息系统连接的事务。本地事务由底层信息系统资源管理器管理。J2EE平台通常不控制或不知道任何组件开始的本地事务。如果没有初始化JTA事务,对于事务性企业信息系统的访问通常在本地事务中。

例如,如果servlet访问JDBC数据库时没有开始JTA事务,数据库的访问将会在本地事务的作用域中,特定于数据库。

当企业信息系统没有使用连接器架构进行整合时,可以使用本地事务。例如,如果对于面向对象的数据库没有连接器资源适配器,J2EE服务器不能将任何JTA事务传播到面向对象数据库,那么任何访问都会在本地事务范围内。正因为这样,应用程序应该使用连接器架构,整合不是J2EE平台一部分的企业信息系统。

1.7.3 EIS层事务指导
企业信息系统,例如数据库,应该在JTA事务的作用域内访问。事务性访问保证数据一致性和完整性,同时保证多个组件通过多个企业信息系统连接执行的操作被组织在一个原子单元中进行。它也被组织成在一个或多个独立的企业信息系统中执行的原子操作。

当不可能进行JTA事务控制时,例如资源管理器不支持JTA,考虑和补偿事务一起,使用资源管理器本地事务。记住,每个本地事务需要显式的提交或回滚。同时,使用本地事务的组件,需要额外的逻辑处理各自企业信息系统的回滚和失败。

1.7.4 补偿事务
补偿事务是一个事务或一组操作,他们undo了之前提交事务的效果。分布式事务可能包含JTA事务和资源管理器本地事务,但本地事务需要显式的管理。可以使用JTA功能的资源管理器可以通过丢弃自事务以来进行的任何修改的方式自动处理回滚操作。但每个资源管理器本地事务,需要补偿事务,在发生回滚时undo本地事务效果。当组件需要访问企业信息系统,但企业信息系统不完全支持JTA事务,或没有被某个J2EE产品支持时,补偿事务是有用的。J2EE平台支持JDBC和JMS访问的JTA事务。EIS访问支持的JTA事务由资源适配器的事务级别决定。一个XATransaction资源适配器自动的支持JTA事务。

LocalTransaction资源适配器在资源管理器本地事务的作用域内访问EIS。执行多个EIS中的原子操作,但部分系统没有参与JTA事务,这是很有挑战性的。补偿事务通过对已经提交的资源管理器本地事务,提供编程式的回滚操作的方式,解决这个问题。补偿事务必须是应用程序逻辑中手动编写的代码,JTA没有提供标准的方式处理他们。

例如,假设一个应用程序的原子操作涉及两个企业信息系统的更新操作:数据库支持JTA事务,但企业资源计划系统不支持。应用程序需要为企业资源计划系统的更新定义补偿事务。如代码8.4所示:



updateERPSystem();

try {

UserTransaction.begin();

updateJDBCDatabase();

UserTransaction.commit();

}

catch (RollbackException ex) {

undoUpdateERPSystem();

}


Code Example 8.4 补偿事务



方法updateERPSystem和updateJDBCDatabase包含在企业信息系统中访问和执行操作的代码。undoUpdateERPSystem方法包含undo updateERPSystem效果的代码,如果JTA事务没有正常提交。

补偿事务有以下容易导致错误的地方:

l         已经提交的事务不是总是看可以undo:代码8.4中。如果一些原因导致undoUpdateERPSystem失败,会导致数据状态的不一致行。

l         服务器崩溃导致原子性被破坏:例如,如果系统在updateERPSystem方法之后立即崩溃,剩下的两个数据库更新操作没有进行,导致结果为部分事务。

l         资源管理器本地事务提交可能违反隔离性:当不支持JTA的资源执行补偿事务时,提交的资源管理器本地事务可能随后会回滚。在代码8.4中,另一个并发的企业信息系统客户端使用提交更新到企业资源计划系统中的数据,而这个数据会随后被悄悄的回滚。换句话说,资源管理器本地事务提交的更新可以其他事务看到,甚至在分布式事务提交前。

1.7.4.1 补偿事务指导
补偿事务代码,应该被封装在bean管理事务的会话企业bean中。会话bean可以自己实现所有的企业信息系统访问逻辑,或代理其他企业bean的一些或全部操作。如果企业bean只负责访问不支持JTA事务的企业信息系统,它的事务属性应该设置为NotSupported,表示JTA事务不能在企业bean中使用。

所有依赖补偿事务的应用程序都必须进行额外的编码,处理潜在的失败和不一致性。补偿事务的额外工作和错误的可能,意味着应该尽量避免使用他们。反之,使用JTA事务在跨越多个组件和企业信息系统时,可以简单和安全的达到ACID的事务属性。

1.7.5 隔离级别
隔离级别定义了到企业信息系统的事务如何相互之间进行隔离。企业信息系统通常支持以下隔离级别:

l         提交读:这个级别防止事务读取其他事务未提交的修改。

l         可重复读:这个级别防止事务从其他事务读取未提交的修改。同时,它保证即使其他事务修改了数据,但同一个事务中多次读取的同一个数据值都是相同的。

l         串行:本级别防止事务读取其他事务未提交的数据,同时,它保证即使其他事务修改了数据,但同一个事务中多次读取的同一个数据值都是相同的。同时,保证如果查询基于一个查询条件取得结果集,同时另一个事务插入满足条件的新数据,重新执行查询将仍然的到相同结果集。

隔离级别和并发性是紧密相关的。隔离级别表示给予EIS管理并发数据访问的度。越低的隔离级别通常允许更高的并发性,但处理内在数据不一致性的消耗更高,需要更负载的逻辑。更高的隔离级别通常允许更简单的逻辑,但代价是系统性能会因为内部EIS数据为了强制ACID事务属性而锁定,导致性能下降。一个有用的指导是,在企业信息系统的访问性能可以接受的情况下,尽量选择高的隔离级别。

为了一致性开了,所有J2EE应用程序访问的企业信息系统,应该使用同样的隔离级别。J2EE规范1.3中,当企业信息系统在JTA事务中访问时,没有定义设置隔离级别的标准方式。如果J2EE产品没有提供配置隔离级别的方法,企业信息系统将使用默认隔离级别。对于大多数关系数据库来说,默认的隔离级别是提交读。

在会话中,事务隔离级别不应该修改,尤其是已经进行了一些操作的情况下。如果你尝试修改隔离级别,一些企业信息系统或强制提交事务。

1.7.6 多个资源管理器的性能
J2EE平台提供的分布式事务支持跨越多个资源管理器,包括JDBC数据库,JMS提供者,和EIS。在同一个事务中使用多个资源管理器对性能的影响是重要的考虑因素。通常,访问多个资源管理器的使用两阶段分布式提交协议,导致额外的处理负载。分布式事务会导致额外的管理负载;例如存在疑问的部分失败的事务必须总是被解决。因此,一个应用程序,应该最小化在同一个事务中访问使用多个资源管理器的情况(例如,将数据合并到一个EIS中)。然而,访问多个事务性资源时,应该使用JTA资源。使用JTA事务的数据完整性和减少编程的好处,肯定大于两阶段提交的额外负载。

1.8 J2EE资源管理器类型
J2EE架构定义了三种类型的资源管理器的事务性行为:JDBC兼容的数据库,J2EE使用连接器的信息系统,和JMS提供者。这三种资源管理器可以在一个分布式事务的作用域中使用。每种类型都有一些不同的需求和事务性语义。本节描述这些资源管理器类型,以及J2EE平台规范在他们之上增加的需求。同时解释了他们的事务语义。

1.8.1 JDBC数据库
J2EE产品需要提供在每个事务中对一个JDBC资源提供事务性访问。对于JDBC资源的事务性访问可以从servlet,JSP页面和企业bean中进行。支持多个组件在同一个事务的作用域中访问同一个JDBC资源。例如,servlet可能开始一个事务,修改数据库,然后调用企业bean中的一些方法修改数据库,所有的这些操作都在同一个事务中进行。支持在同一个事务中访问多个数据库的产品必须使用特定于平台的API实现。使用这些特性的应用程序不能移植。

1.8.2 JMS提供者
J2EE产品需要在一个事务中至少支持一个JMS提供者。事务性访问JMS提供者,可以在servlet,JSP页面和企业bean中进行。和JDBC连接一样,多个组件,可能在不同的层次中,必须可以在同一个事务作用域中访问JMS提供者。和JDBC数据库一样,在一个事务中访问多个JMS提供者需要特定于平台的API,牺牲了可移植性。

JMS消息传输的事务性语义引用消息本身的传输,不需要任何传输可能导致的副作用。每个事务将一组消息生产者和消息消费者组织到一个工作的原子单元中。在一个事务中生产的消息不会立即发送,相反,他们在事务提交时发送。如果事务被回滚,消息就被丢弃。类似的,在事务提交前,消息不会被消费。如果事务回滚,消息会被redelivered,并且仍然可以进行消费。

在JMS中,事务从不会在JMS的发送者和接收者之间传播。换句话说,JMS消息的发送者和接收者,从来都不在同一个会话中。JMS事务语义提供发送和接收消息的原子性。例如,如果应用程序在事务T1中发送两个消息,两个消息要么都发送,要么都丢弃。在接收端,如果两个消息都在不同的事务T2中交付,JMS提供者会尝试重新交付消息,如果事务T2发生回滚。这是一些分开的操作,同时事务T1和T2是彼此分离的。

对于J2EE应用程序来说,JMS的事务语义可能导致潜在的死锁现象。假设应用程序A发送一个JMS消息到应用程序B,然后挂起,指导接收到应用程序B的消息(例如,一个通知消息)。如果这个工作在一个事务中进行,应用程序A将会无限等待,因为从A到B的消息不会发送,直到事务提交。为了避免这种情况,需要将应用程序A中消息的发送和接收分散到两个事务中进行。

1.8.3 J2EE连接器架构
J2EE连接器架构,定义了整合不同EIS资源的标准架构。它定义了J2EE服务器和资源适配器之间的协议,它是特定EIS资源的系统级软件驱动。这些标准的协议可以插入应用程序服务器和EIS中。

资源适配器可以支持三种不同的事务级别:

l         NoTransaction:不支持事务

l         LocalTransaction:支持资源管理器本地事务

l         XATransaction:资源适配器支持XA和JTA XAResource接口。一个支持JTA XAResource的资源适配器,必须同时支持LocalTransaction。

如果NoTransaction资源适配器作为JTA事务的一部分使用,那么通过资源适配器执行的操作将会独立于事务。换句话说,即使JTA事务本身回滚了,这个操作也不会回滚。

如果在JTA事务中使用LocalTransaction资源适配器,同一个事务中不能再使用其他事务性资源(例如,JDBC或JMS)。因为LocalTransaction资源适配器不支持两阶段提交协议,因此不能在同一个事务中和其他事务性资源混合。

XATransaction资源适配器是最灵活的,可以在同一个JTA事务中和其他事务性资源混合。例如,J2EE应用程序可以在同一个事务中更新JDBC数据库,发送JMS消息,访问EIS资源。注意,如果资源适配器只支持LocalTransaction,这种场景是非法的,因为LocalTransaction资源适配器不能在同一个事务中和其他事务性资源混合。如果资源适配器只支持NoTransaction,那么本场景仍然合法。然而,对于EIS的访问不是JTA事务的一部分。表8.1总结了不同事务级别下资源适配器的行为:






应用程序为了最大的灵活性和数据完整性应该在任何可能的使用使用XATransaction。如果XATransaction资源适配器无法得到,LocalTransaction资源适配器可以提供类似的事务行为,只要在事务中只有一个资源适配器。

如果应用程序需要在一个事务中访问多个资源,那么可以使用补偿事务。只有当对EIS的事务性访问不重要时,应用程序才可以使用NoTransaction资源适配器。

1.9 总结
本章提供了在J2EE平台中使用事务的指导。它描述了每种J2EE组件类型的事务性模型,包括:应用程序客户端,JSP页面和servlet,企业bean和企业信息系统。

J2EE平台为编写事务性应用程序提供了强大的支持。它包含Java事务API,允许应用程序使用一种独立于特定实现的方式访问事务,提供了通过声明方式指定应用程序事务需求的方法。这种能力让事务管理的重担从J2EE组件提供者转移到了J2EE产品提供者。应用程序组件提供者可以关注于指定需要的事务行为,并且依赖J2EE产品实现行为。
[/size]

你可能感兴趣的:(java,应用服务器,网络应用,配置管理,企业应用)