JTS 是一个 组件事务监视器(component transaction monitor)。TPM 是一个程式,它代表应用程式协调分散式事务的执行。TPM 与资料库出现的时间长短差不多;在 60 年代后期,IBM 开发了 CICS,至今人们仍在使用。经典的(或者说 程式化)TPM 管理被程式化定义为针对事务性资源(比如资料库)的操作序列的事务。随着分散式对象协定,如 CORBA、DCOM 和 RMI 的出现,人们希望看到事务更面向对象的前景。将事务性语义告知面向对象的组件要求对 TPM 模型进行扩展 ― 在这个模型中事务是按照事务性对象的调用方法定义的。
基本介绍
- 中文名组件事务监视器(JTS)
- 外文名component transaction monitor
- 别称CTM
- 属性高科技工业产品
- 组件储存事务性资源管理器
- 最先开发商欧美地区
概述
JTS 只是一个组件事务监视器(有时也称为 对象事务监视器(object transaction monitor)),或称为 CTM。
JTS 和 J2EE 的事务支持设计受 CORBA 对象事务服务(CORBA Object Transaction Service,OTS)的影响很大。实际上,JTS 实现 OTS 并充当 java 事务 API(Java Transaction API)― 一种用来定义事务边界的低级 API ― 和 OTS 之间的接口。使用 OTS 代替创建一个新对象事务协定遵循了现有标準,并使 J2EE 和 CORBA 能够互相兼容。
乍一看,从程式化事务监视器到 CTM 的转变好像只是术语名称改变了一下。,差别不止这一点。当 CTM 中的事务提交或回滚时,与事务相关的对象所做的全部更改都一起被提交或取消。但 CTM 怎幺知道对象在事务期间做了什幺事?象 EJB 组件之类的事务性组件并没有 commit() 或 rollback() 方法,它们也没向事务监视器注册自己做了什幺事。那幺 J2EE 组件执行的操作如何变成事务的一部分呢?
透明的资源徵用
当应用程式状态被组件操纵时,它仍然存储在事务性资源管理器(例如,资料库和讯息伫列伺服器)中,这些事务性资源管理器可以注册为分散式事务中的资源管理器。在第 1 部分中,我们讨论了如何在单个事务中徵用多个资源管理器,事务管理器如何协调这些资源管理器。资源管理器知道如何把应用程式状态中的变化与特定的事务关联起来。
但这只是把问题的焦点从组件转移到了资源管理器 ― 容器如何断定什幺资源与该事务有关,可以供它徵用?请考虑下面的代码,在典型的 EJB 会话 bean 中您可能会发现这样的代码
清单 1. bean 管理的事务的透明资源徵用
InitialContext ic = new InitialContext();
UserTransaction ut = ejbContext.getUserTransaction();
ut.begin();
DataSource db1 = (DataSource) ic.lookup("java:comp/env/OrdersDB");
DataSource db2 = (DataSource) ic.lookup("java:comp/env/InventoryDB");
Connection con1 = db1.getConnection();
Connection con2 = db2.getConnection();
// perform updates to OrdersDB using connection con1
// perform updates to InventoryDB using connection con2
ut.commit();
注意,这个示例中没有徵用当前事务中 JDBC 连线的代码 ― 容器会为我们完成这个任务。我们来看一下它是如何发生的。
类型
当一个 EJB 组件想访问资料库、讯息伫列伺服器或者其它一些事务性资源时,它需要到资源管理器的连线(通常是使用 JNDI)。而且,J2EE 规范只认可三种类型的事务性资源 ― JDBC 资料库、JMS 讯息伫列伺服器和“其它通过 JCA 访问的事务性服务”。后面一种服务(比如 ERP 系统)必须通过 JCA(J2EE Connector Architecture,J2EE 连线器体系结构)访问。对于这些类型资源中的每一种,容器或提供者都会帮我们把资源徵调到事务中。
在清单 1 中, con1 和 con2 好象是普通的 JDBC 连线,比如那些从 DriverManager.getConnection() 返回的连线。我们从一个 JDBC DataSource 得到这些连线,JDBC DataSource 可以通过查找 JNDI 中的数据源名称得到。EJB 组件中被用来查找数据源( java:comp/env/OrdersDB )的名称是特定于组件的;组件的部署描述符的 resource-ref 部分将其映射为容器管理的一些应用程式级 DataSource 的 JNDI 名称。
隐藏的 JDBC 驱动器
每个 J2EE 容器都可以创建有事务意识的池态 DataSource 对象,但 J2EE 规范并不向您展示如何创建,因为这不在 J2EE 规范内。浏览 J2EE 文档时,您找不到任何关于如何创建 JDBC 数据源的内容。相反,您不得不为您的容器查阅该文档。创建一个数据源可能需要向属性或配置档案添加一个数据源定义,或者也可以通过 GUI 管理工具完成,这取决于您的容器。
每个容器(或连线池管理器,如 PoolMan)都提供它自己的创建 DataSource 机制,JTA 魔术就隐藏在这个机制中。连线池管理器从指定的 JDBC 驱动器得到一个 Connection ,但在将它返回到应用程式之前,将它与一个也实现 Connection 的虚包包在一起,将自己置入应用程式和底层连线之间。当创建连线或者执行 JDBC 操作时,包装器询问事务管理器当前执行绪是不是正在事务的上下文中执行,如果事务中有 Connection 的话,就自动徵用它。
其它类型的事务性资源,JMS 讯息伫列和 JCA 连线器,依靠相似的机制将资源徵用隐藏起来,使用户看不到。如果要使 JMS 伫列在部署时对 J2EE 应用程式可用,您就要使用特定于提供者的机制来创建受管 JMS 对象(伫列连线工厂和目标),然后在 JNDI 名称空间内发布这些对象。提供者创建的受管对象包含与 JDBC 包装器(由容器提供的连线池管理器添加)相似的自动徵用代码。
透明的事务控制
两种类型的 J2EE 事务 ― 容器管理的和 bean 管理的 ― 在如何启动和结束事务上是不同的。事务启动和结束的地方被称为 事务划分(transaction demarcation)。清单 1 中的示例代码演示了 bean 管理的事务(有时也称为 编程(programmatic)事务)。Bean 管理的事务是由组件使用 UserTransaction 类显式启动和结束的。通过 ejbContext 使 UserTransaction 对 EJB 组件可用,通过 JNDI 使其对其它 J2EE 组件可用。
容器根据组件的部署描述符中的事务属性代表应用程式透明地启动和结束容器管理的事务(或称为 宣告式事务(declarative transaction))。通过将 transaction-type 属性设定为 Container 或 Bean 您可以指出 EJB 组件是使用 bean 管理的事务性支持还是容器管理的事务性支持。
使用容器管理的事务,您可以在 EJB 类或方法级别上指定事务性属性;您可以为 EJB 类指定预设的事务性属性,如果不同的方法会有不同的事务性语义,您还可以为每个方法指定属性。这些事务性属性在装配描述符(assembly descriptor)的 container-transaction 部分被指定。清单 2 显示了一个装配描述符示例。 trans-attribute 的受支持的值有
Supports
Required
RequiresNew
Mandatory
NotSupported
Never
trans-attribute 决定方法是否支持在事务内部执行、当在事务内部调用方法时容器会执行什幺操作以及在事务外部调用方法时容器会执行什幺操作。最常用的容器管理的事务属性是 Required 。如果设定了 Required ,过程中的事务将在该事务中徵用您的 bean,但如果没有正在运行的事务,容器将为您启动一个。在这个系列的第 3 部分,当您可能想使用每个事务属性时,我们将研究各个事务属性之间的区别。
清单 2. EJB 装配描述符样本
<assembly-descriptor>
...
<container-transaction>
<method>
<ejb-name>MyBean</ejb-name>
<method-name></method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>MyBean</ejb-name>
<method-name>updateName</method-name>
</method>
<trans-attribute>RequiresNew</trans-attribute>
</container-transaction>
...
</assembly-descriptor>
功能强大,但很危险
与清单 1 中的示例不同,由于有宣告式事务划分,这段组件代码中没有事务管理代码。这不仅使结果组件代码更加易读(因为它不与事务管理代码混在一起),而且它还有另一个更重要的优点 ― 不必修改,甚至不必访问组件的原始码,就可以在应用程式装配时改变组件的事务性语义。
儘管能够指定与代码分开的事务划分是一种非常强大的功能,但在装配时做出不好的决定会使应用程式变得不稳定,或者严重影响它的性能。对容器管理的事务进行正确分界的责任由组件开发者和应用程式装配人员共同担当。组件开发者需要提供足够的文档说明组件是做什幺的,这样应用程式部署者就能够明智地决定如何构建应用程式的事务。应用程式装配人员需要理解应用程式中的组件是怎样相互作用的,这样就可以用一种既强制应用程式保持一致又不削弱性能的方法对事务进行分界。在这个系列的第 3 部分中我们将讨论这些问题。
透明的事务传播
在任何类型的事务中,资源徵用都是透明的;容器自动将事务过程中使用的任意事务性资源徵调到当前事务中。这个过程不仅扩展到事务性方法使用的资源(比如在清单 1 中获得的资料库连线),还扩展到它调用的方法(甚至远程方法)使用的资源。我们来看一下这是如何发生的。
容器用执行绪与事务相关联
我们假设对象 A 的 methodA() 启动一个事务,然后调用对象 B 的 methodB() (对象 B 将得到一个 JDBC 连线并更新资料库)。 B 获得的连线将被自动徵调到 A 创建的事务中。容器怎幺知道要做这件事?
当事务启动时,事务上下文与执行执行绪关联在一起。当 A 创建事务时, A 在其中执行的执行绪与该事务关联在一起。由于本地方法调用与主调程式(caller)在同一个执行绪内执行,所以 A 调用的每个方法也都在该事务的上下文中。
橱中骸骨
如果对象 B 其实是在另一个执行绪,甚至另一个 JVM 中执行的 EJB 组件的存根,情况会怎样?令人吃惊的是,远程对象 B 访问的资源仍将在当前事务中被徵用。EJB 对象存根(在主调程式的上下文中执行的那部分)、EJB 协定(IIOP 上的 RMI)和远端的骨架对象协力要使其透明地发生。存根确定调用者是不是正在执行一个事务。如果是,事务标识,或者说 Xid,被作为 IIOP 调用的一部分与方法参数一起传播到远程对象。(IIOP 是 CORBA 远程-调用协定,它为传播执行上下文(比如事务上下文和安全性上下文)的各种元素而备;关于 RMI over IIOP 的更多信息,请参阅 参考资料。)如果调用是事务的一部分,那幺远程系统上的骨架对象自动设定远程执行绪的事务上下文,这样,当调用实际的远程方法时,它已经是事务的一部分了。(存根和骨架对象还负责开始和提交容器管理的事务。)
事务可以由任何 J2EE 组件来启动 ― 一个 EJB 组件、一个 servlet 或者一个 JSP 页面(如果容器支持的话,还可以是一个应用程式客户机)。这意味着,应用程式可以在请求到达时在 servlet 或者 JSP 页面中启动事务、在 servlet 或者 JSP 页面中执行一些处理、作为页面逻辑的一部分访问多个伺服器上的实体 bean 和会话 bean 并使所有这些工作透明地成为一个事务的一部分。图 1 演示了事务上下文怎样遵守从 servlet 到 EJB,再到 EJB 的执行路径。