理解EJB组件
你现在应该已经熟悉了整个EJB体系结构及其主要的部件。这一节更详细地描述了这些部件,并解释它们运行时的行为语意。
Home接口
EJB库使用enterprise bean的客户端通过它的home接口创建它的实例。Home接口包含一个或多个用来创建enterprise bean实例的create()方法。这个home接口不是由bean来实现,而是通过称为home object的类来实现。一个home object的实例在服务器中实例化,使得客户端可以访问它们。
定位home object 一个home object的引用被放在名字服务中,客户端能通过JNDI访问它。EJB服务器一般提供某种名字空间的实现,虽然有时可以使用外部的名字空间。在这两种情况下客户端都必须知道名字空间的位置以及JNDI的上下文类。例如,一个客户端的applet可能接收名字空间和JNDI上下文类作为applet的参数。
除了提供位置和类名,客户端也必须知道在名字树中定位home object.这些必须在客户端启动时提供。当部署者把enterprise bean部署进EJB服务器中时,他必须可以以参数形式指定名字树,如ejb/accounting/AccountsPayable.客户端必须获得这个完整的路径名来定位并获得AccountsPayable home object的引用。并不是说客户端通过JNDI获得容器。客户端使用JNDI查找home接口的实现。Home接口的实现由某个特殊的container来提供,但这是该容器厂商的细节,enterprise bean开发者和客户端应该忽略它。
Home接口中的方法
Enterprise bean开发者定义ejbCreate()方法的同时必须在home接口中声明与其相应的create()方法。实体bean可以包含finder方法以使得客户端能定位已有的实体bean. Home接口是通过继承javax.ejb.EJBHome来定义的。该接口包含如下的方法:
public interface javax.ejb.EJBHome extends Remote {
public EJBMetaData getEJBMetaData() throws RemoteException;
public void remove(Handle handle) throws
RemoteException,RemoveException;
public void remove(Object primaryKey) throws
RemoteException,RemoveException;
一个bean的home接口可以象下面这样:
public interface myHome extends EJBHome {
public myRem create() throws RemoteException,CreateException;
public myRem create(String str) throws
RemoteException,CreateException;
其中
public interface myRem extends EJBObject { … }
容器开发商负责提供实现home接口的home对象,因为只有开发商才能实现存贮enterprise bean的库的编码。
容器
定义容器在理解EJB规范时容器这个术语并不应从字面上简单地理解为类,而是一层代替bean执行相应服务的接口。容器开发商提供运行在EJB服务器中一套完成这些功能的工具和接口。
这些服务包括:
·与二级存储中交换(对会话bean)
·持久性管理(对实体bean)
·实现创建和查找服务的home object的可用性
·在可通过JNDI访问的名字空间home object的可视性
·正确的创建、初始化和删除bean
·保证商业方法正确地运行在事务上下文中
·实现某一基本的安全服务
·从home object和EJBObject上的RMI上产生stub和skeleton
容器和EJBObject对规范经常引用由容器或EJBObject提供的服务。这些服务只是用来说明而不暗示特殊类的服务需求。支持enterprise bean的EJBObject和容器类都由容器开发商提供。这些类必须完成bean容器的功能。对bean来说容器和EJBObject是不同的入口点,对某个特殊的服务提供支持独特的能力。例如,容器通过读部署描述符来知道应用于bean方法的事务属性。然而,这些商业方法通过EJBObject调用。
EJBObject必须与容器通讯来确定调用商业方法的事务上下文。确定以后,EJBObject在调用商业方法以前建立事务上下文。重要的是EJBObject 和容器的协同工作来实现容器所需的事务。容器厂商提供二者的实现,但对二者的功能分割却是自由的。与home接口的关系
目前厂商提供工具来读home 接口并产生作为容器的home object.在这种情况下厂商对每个enterprise bean类使用不同的容器类。容器厂商可以使用其它的实现策略,如一个容器类实现多个home接口,甚至一个标准的容器类创建独立的home object实现。唯一的必要条件是容器厂商必须使客户端能通过JNDI访问home object. 客户端和bean开发者都不需关心容器和home object的实现细节。
Enterprise JavaBean
Enterprise bean是开发者编写的提供应用程序功能的类。开发者可以选择创建会话bean或实体bean,通过实现不同的接口声明其部署描述符来加以区分。
对于会话bean:
public class myBean implements javax.ejb.SessionBean …
对于实体bean:
public class myBean implements javax.ejb.EntityBean …
客户端不会直接访问enterprise bean中的任何方法。客户端通过EJBObject 间接调用bean中的方法,EJBObject就象一个代理一样。在把调用通过EJBObject传递时,容器开发商通过包装编码插入其自己的功能,这称为方法插入。方法插入的一个例子是为每个方法调用创建一个新的事务上下文,当方法返回到EJBObject时提交或回滚事务。
当容器厂商的工具在安装bean产生stub和skeleton时,它产生bean的EJBObject一个stub和skeleton.实际上它并不创建bean本身的stub和skeleton,因为bean不会通过网络被访问。EJBObject是真正的网络对象。Bean 是包含应用相关的商业编码的代表。
容器也可以调用bean中的某个方法。例如,容器保证当一个bean 实例生成后,home object中的create()的任何参数会传递bean相应的ejbCreate()方法。 Enterprise bean还有其它的接口和要求。然而,会话bean和实体bean的要求是不同的。这些在随后详述会话和实体bean的章节中会cover.
Remote Interface
编写完enterprise bean后开发者创建了一个客户端可访问创建方法的home interface,在home interface中每一个create()方法在相应的bean中都必须有一个ejbcreate()方法。同样,开发者必须创建描述客户端能够访问的商业方法的remote interface。
因为所有的客户端调用都通过EJBObject,因此实现这个接口的是EJBObject而不是home object. Remote interface中列出的方法名和signature必须和实现bean的方法名和signature相同。这不同于home interface--方法signature是一样的,而名字却不同。以下是一个remote interface的例子:
public interface Account extends javax.ejb.EJBObject {
public void deposit(double amount) throws RemoteException;
public void withdraw(double amount) throws RemoteException;
public double balance() throws RemoteException;
}
所有声明的方法都必须抛出一个RemoteException例外,因为规范要求客户端stub是RMI兼容的。但这并不意味着排除了用其它的传输方式的stub/skeleton实现,如CORBA/IIOP.
Remote interface继承javax.ejb.EJBObject接口,增加了额外的方法要求。
EJBObject
EJBObject是网络上可视的对象,包含stub和skeleton,作为bean的代理。Bean的remote interface继承了EJBObject接口,而EJBObject类实现这个remote interface,使得bean类有自己的EJBObject类。对每个bean类有一个定制的EJBObject类。
如下是EJBObject接口的定义,被bean的remote interface继承:
public interface javax.ejb.EJBObject extends java.rmi.Remote {
public EJBHome getEJBHome() throws RemoteException;
public Object getPrimaryKey() throws RemoteException;
public Handle getHandle() throws RemoteException;
public void remove() throws RemoteException,RemoveException;
public boolean isIdentical(EJBObject other) throws RemoteException;
}
实现这个接口的EJBObject类是一个RMI服务器对象,因为它是他实现了一个RMI remote interface.注意bean本身不是一个remote object,在网络上是不可视的。当容器实例化了这个EJBObject类时,容器会初始化bean实例的引用,使得它能正确地delegate商业方法调用。
厂商的实现是维护EJBObject实例和bean实例的一对一关系。因为remote interface包含EJBObject接口的方法,所以bean不必显式地实现这个接口,虽然它提供了列出的商业方法的实现。因为EJBObject必须正式地实现bean的remote interface,容器在bean安装时产生EJBObject的源代码,这些产生的源代码实现了bean的remote interface.典型的EJBObject有一个独特的类名,作为EJBObject类和bean的联系。
会话bean
会话bean是一种通过home interface创建并对客户端连接专有的enterprise bean.会话bean实例一般不与其它客户端共享。这允许会话bean维护客户端的状态。会话bean的一个例子是购货车,众多顾客可以同时购货,想他们自己的购货车中加东西,而不是向一个公共的购货车中加私人的货物。定义一个会话bean
可以通过定义一个实现javax.ejb.SessionBean接口的类来创建一个会话bean.该接口定义如下:
public interface javax.ejb.SessionBean extends javax.ejb.EnterpriseBean {
public void ejbActivate() throws RemoteException;
public void ejbPassivate() throws RemoteException;
public void ejbRemove() throws RemoteException;
public void setSessionContext(SessionContext context)
throws RemoteException;
}
javax.ejb.EnterpriseBean是一个空接口,是会话bean和实体bean的超类。
会话bean的交换
容器开发商可以实现把会话bean的实例从主存移到二级存储中的交换机制,这可以增加一段时间内实例化的会话bean的总数。容器维护一个bean的时间期限,当某个bean的不活动状态时间达到这个阙值,容器就把这个bean拷贝到二级存储中并从主存中删除。
容器可以使用任何机制来实现bean的持久性存储。最常用的方式是通过bean的串行化。Bean开发者在bean中应避免使用transient fields。EjbActivate()和ejbPassivate()用来维护这个fields值。
活化和钝化
为了支持厂商提供会话bean的交换,规范定义了钝化--把bean从主存转移到二级存储的过程,活化--把bean恢复到主存中去的过程。在SessionBean接口中声明的EjbActivate()和ejbPassivate()方法,允许容器通知已经被活化的bean它将要被钝化。Bean开发者可以用这些方法释放和恢复处于钝化状态的bean所占有的值、引用和系统资源。一个可能的例子是数据库连接,作为有限的系统资源,不能被钝化的bean使用。
有了这些方法就使得不必在使用transient.事实上,使用transient可能是不安全的,因为串行化机制自动地把值设为null或0。而通过ejbActivate()和ejbPassivate()方法显式地设置这些fields更好一些。依靠Java的串性化机制把transient fields设成null也是不可移植的,因为当bean部署在不使用Java的串性化机制获得持久性的EJB容器中时该行为会发生改变。如果容器不提供交换,那么这些方法将永远不会被调用。
当客户端调用bean的商业方法时钝化的bean被激活。当EJBObject收到方法调用的请求时,它通知容器需要活化的bean.当活化完成时,EJBObject代理对bean的方法调用。
如果bean 参与一个事务,那么它不能被钝化。把bean放在主存中更有效率,因为事务通常在很短的时间内完成。如果bean没有钝化前必须释放或活化前必须重置的状态,那么这些方法可置空。在大多数情况下,bean开发者不必在这些方法中做任何事。
会话bean的状态管理
会话bean的部署描述符必须声明该bean是有状态或无状态的。一个无状态bean是在方法调用间不维护任何状态信息的bean。通常,会话bean的优点是代替客户端维护状态。
然而,让会话bean无状态也有一个好处。无状态bean不能被钝化。因为它不维护状态,所以没有需要保存的信息。容器可以删除bean的实例。客户端永远不会知道无状态bean的删除过程。客户端的引用是EJBObject.如果客户端稍后又调用了一个商业方法,则EJBObject通知容器在实例化一个新的会话bean.因为没有状态,因此也没有信息需要恢复。
无状态bean可以在客户端间共享,只是在某一时刻只能有一个客户端执行一个方法。因为在方法调用间没有需要维护的状态,所以客户端可使用任何无状态bean的实例。这使得容器可以维护一个较小的可服用bean的缓冲池,节省主存。因为无状态bean在方法调用间不能维护状态,因此从技术上讲在home interface的create()方法不应有参数。在创建时向bean传递参数意味着在ejbCreate()返回时需要维护bean的状态。而且,经由EJBObject调用商业方法的结果使得容器必须能重创建一个无状态的bean.这时在开始创建bean时的参数就不存在了。厂商的安装工具应该能检查home interface的无状态对话bean以保证其不包含带参数的create()方法。
实体bean
实体bean的角色
实体bean用来代表底层的对象。最常用的是用实体bean代表关系库中的数据。一个简单的实体bean可以定义成代表数据库表的一个记录,也就是每一个实例代表一个特殊的记录。更复杂的实体bean可以代表数据库表间关联视图。在实体bean中还可以考虑包含厂商的增强功能,如对象--关系映射的集成。
通常用实体类代表一个数据库表比代表多个相关联的表更简单且更有效。反过来可以轻易地向实体类的定义中增加关联,这样可以最大地复用cache并减小旧数据的表现。
实体bean和对话bean的比较
看起来会话bean好象没什么用处,尤其对于数据驱动的应用程序。当然事实并不是这样。因为实体bean(譬如说)代表底层数据库的一行,则实体bean实例和数据库记录间就是一对一的关系。因为多个客户端必须访问底层记录,这意味着,不同于会话bean,客户端必须共享实体bean。因为是共享的,所以实体bean不允许保存每个客户端的信息。会话bean允许保存客户端的状态信息,客户端和会话bean实例间是一对一的。实体bean允许保存记录的信息,实体bean实例和记录间是一对一的。一个理想的情况是客户端通过会话bean连接服务器,然后会话bean通过实体bean访问数据库。这使得既可以保存客户端的信息又可以保存数据库记录的信息。
同时会话bean也不能提供在相同或不同的EJB类调用间进行全局的事务控制。没有会话bean,应用程序开发者(客户端开发者)就必须理解EJB类的事务要求,并使用客户端的事务划分来提供事务控制。EJB的主要好处就是应用开发者不需知道EJB类的事务需求。一个会话bean可以代表一个商业操作,进行事务控制,不需要客户端进行事务划分。
Finder方法
通过home或remote interface创建和删除bean的实例,对实体bean和会话bean来说有不同的含义。对会话bean来说,删除意味着从容器中删除,不能再使用,并且其状态信息也丢失了。对于实体bean,删除意味着底层数据库记录被删除了。因此,一般不把删除作为实体bean生命周期的一部分。
创建一个实体bean意味着一个记录被插进数据库中。与删除操作类似,创建操作通常也不作为实体bean生命周期的一部分。客户端访问实体bean需要先找到它。除了create()方法,一个实体bean的home interface还有finder方法。客户端需要根据应用程序的限制来识别一个特殊的数据库记录。例如:
public interface AccountHome extends EJBHome {
public Account findByFirstLast(String first, String last)
throws RemoteException,FinderException;
public Account findByAccountNumber(String acctNum)
throws RemoteException,FinderException;
}
当客户端调用home object的任何方法,容器把调用传递到实体bean的相应方法中。
Public class myEntityBean implements EntityBean {
…
public Obejct ejbFindByFirstLast(String first, String last) {
//runs appropriate singleton SELECT statement
//returns primary key for selected row
}
public Obejct ejbFindByAccountNumber(String acctNum) {
//runs appropriate singleton SELECT statement
//returns primary key for selected row
}
}
一个较好的方法是把finder方法当成数据库的SELECT语句,而动态SQL参数相当于方法的参数。注意home interface中的finder方法向客户端返回一个对EJBObject的远程引用。Bean中的Finder方法向容器返回一个唯一的标识符,称为主键。容器用这个主键实例化一个代表选定的记录的EJBObject。不论如何实现finder方法,容器都用这个主键代表这个选定的记录,由实体类来决定如何用唯一的标识符来代表记录。
由可能一个finder方法得到满足SELECT语句条件的多个记录。这种情况下bean的finder方法返回一个主键的枚举类型。Home interface的Finder方法定义成向客户端返回EJBObject引用的枚举类型。
Public interface AccountHome extends EJBHome {
…
public Enumeration findByCompany(String companyName)
throws RemoteException,FinderException;
}
public class myEntityBean implements EntityBean {
…
public Enumeration ejbFindByCompany(String companyName) {
//runs appropriate SELECT statement
//returns an Enumeration of primary keys
}
}
主键
主键这个词有可能被曲解。把它理解为唯一的标识符更恰当些。当实体bean代表一个数据库记录时,主键可能是该记录的组合键。对于每个实体bean的实例,有一个相应的EJBObject.当一个EJBObject与一个实体bean实例对应时,该实例的主键保存在EJBObject中。
这时说该实体bean的实例有一个标识符。当客户端调用home object的finder方法时,容器会用没有标识符的实体bean的实例来执行这个请求。容器可能为此维持一个甚至多个匿名的实例。不论如何实现finder方法,都必须向容器返回底层数据的主键,如数据库的记录。如果多个记录满足条件,那么就返回多个主键。当容器得到主键后,它会用该主键初始化一个EJBObject.容器也可以初始化一个与每个EJBObject关联的实体bean的实例。因为底层记录的标识符在EJBObject中保存,因此在bean实例中没有状态。因此,容器可以在EJBObject上调用商业方法时再实例化bean,以节省内存资源。
当finder方法向容器返回主键时,容器首先会检查该主键的EJBObject是否已经存在。如果该主键的EJBObject已经存在,那么容器不会创建一个新的EJBObject,而是向客户端返回这个已存在的EJBObject的引用。这样就保证了每个记录只有一个EJBObject的实例,所有的客户端共享EJBObject.
主键只是在该类中唯一地标识bean的实例,容器负责保证其范围。应该明确finder方法只是从数据库中取出数据的主键,而不包含其它的数据项。也可能调用finder方法后不产生任何实体bean的实例,只产生包含该主键的EJBObject,当客户端调用EJBObject的方法时在产生并导入实体bean的实例。 Home object保证客户端可以访问以下方法:
public myRem findByPrimaryKey(Obejct key) throws …;
EJBObject提供以下方法的一个实现:
Public Object getPrimaryKey();
客户端能在任何时候获得实体bean的主键,并且以后可以使用该主键通过home interface重建对实体的引用。主键类的类型在部署描述符中指定。Bean开发者可以用任何类类型来表示主键。唯一的要求是类必须实现serializable,因为主键可能在客户和服务器间传递。
实体bean的内外存交换
现在应该来看看javax.ejb.EntityBean接口。
public interface javax.ejb.EntityBean extends EnterpriseBean {
public void ejbActivate() throws RemoteException;
public void ejbPassivate() throws RemoteException;
public void ejbRemove() throws RemoteException,RemoveException;
public void setEntityContext(EntityContext ctx) throws RemoteException;
public void unsetEntityContext() throws RemoteException;
public void ejbLoad() throws RemoteException;
public void ejbStore() throws RemoteException;
}
活化和钝化的过程与会话bean类似。然而,不在事务中的实体bean是无状态的;其状态总是和底层的数据同步的。如果我们象钝化会话bean那样钝化实体bean,则当钝化无状态实体bean时只会删除它。但是因为容器调用finder方法需要匿名的实体bean,容器可能为此把不活动的实体bean钝化到一个私有内存池中。一旦从EJBObject中删除实体bean,则同时删除了标识符(主键关联)。
当客户端调用没有相关的实体bean的EJBObject的商业方法时,容器就可能用这个内存池重新分配实体bean. 注意这个内存池中的bean没有标识,可以被任何EJBObject重用。容器可以可以不维护任何有EJBObject的实体bean,除非有一个商业方法在通过EJBObject被调用。如果实体bean在事务中则需保持其与EJBObject的关联。
自管理的持久性
因为实体bean代表底层的数据,因此我们需要把数据从数据从数据库中取出然后放在bean中。当容器第一次把一个实体bean的实例与EJBObject关联时,它就开始了一个事务并调用了这个bean的ejbLoad()方法。在这个方法中开发者必须提供从数据库中取出正确的数据并把它放在bean中。当容器将要提交一个事务它首先调用bean的ejbStrore()方法。这个方法负责向数据库中回写数据。我们称之为自管理持久性,因为bean方法中的代码提供了这种同步。
当ejbLoad()方法完成时,bean有可能与底层数据库不一致。商业方法的调用触发了与EJBObject关联的bean的分配,然后在事务中执行的ejbLoad()必须在部署描述符中声明。根据接收到的方法调用请求,EJBObject和容器一起建立一个事务上下文。容器分配EJBObject的bean并调用bean的ejbLoad()方法。这个方法现在运行在事务上下文中。这个事务上下文传递给数据库,根据部署描述符中指定的孤立性级别,这个事务锁定数据库中被访问的数据。
只要事务上下文活动,数据库中的数据就一直保持锁定状态。当客户端或容器提交事务时,容器首先调用bean的ejbStore()方法,把bean中的数据回写到数据库中。
相应的数据库记录在ejbLoad()和ejbStore()间保持锁定保证了bean和数据库间的同步。其间可以进行不同的商业方法调用。而且,ejbLoad()和ejbStore()明确地区分了事务边界,事务中可以进行任何商业方法调用。事务的持续时间由部署描述符决定,也可能由客户端决定。注意不必使用ejbActivate()和ejbPassivate()方法来执行与数据库间的同步。
容器管理的持久性
如果部署描述符声明bean使用容器管理的持久性,则不用ejbLoad()和ejbStore()来访问数据库。容器会把数据从数据库中导入到bean中,然后调用bean的ejbLoad()方法来完成从数据库中接收数据。
同样地,容器调用bean的ejbStore()方法来完成把数据回写到数据库中。这些方法实际上没有执行任何数据库操作。当开发商用复杂的工具来提供容器管理持久性时,如自动产生能进行对象--关系映射的实体bean类,规范规定了厂商必须提供的容器管理实体持久性的最小需求集。
部署描述符可以指定bean的一个public域来实现与数据库列简单映射。容器使用部署描述符读出bean的这个public域并写到相应的列,或从数据库列中读出数据写到public域中。容器管理的持久性对EJB开发者来说是非常好的服务,且不需对象--关系影射等其他复杂的机制,开发者会发现它比自管理的持久性更有效率。
部署描述符
区分EJB开发的角色
EJB开发中两个主要的角色是bean开发者和bean部署者。有很多属性开发者不能预知,如数据库的网络地址,使用的数据库驱动程序等等。部署描述符作为由开发定义的特性表,由部署者添入正确的值。部署描述符有标准的格式,在开发和部署环境中是可移植的,甚至在不同EJB平台间也是可移植的。 Enterprise bean的行为控制
除了为开发和部署的协同提供一个标准的属性单,部署描述符也应包含bean应如何执行有关事务和安全的细节信息。一些如访问控制链(ACL)等属性,也应该由部署者来调整以保证适当的用户能在运行时使用bean.其它属性,如事务信息,有可能完全由开发者指定,因为一般由开发者创建数据库访问代码,并熟知bean的方法应如何运行事务。定义部署描述符
部署描述符是一个标准的Java类。创建一个实例,导入数据,然后串行化。这个串行化的部署描述符放在一个jar文件中并和enterprise bean类一起送到部署环境。部署者读取这个串行化的部署描述符,可能修改一些属性值,然后使用这个修改后的部署描述符来安装enterprise bean.
下面是部署描述符的一部分内容。它是两个其它的部署描述符类的超类。实际上超类是描述这个bean的描述符。
Javax.ejb.deployment.DeploymentDescriptor
·bean home name
·bean class name
·home interface class name
·remote interface class name
·environment properties
·control descriptors
·access control list
DeploymentDescriptor有两个子类:
javax.ejb.deployment.SessionDescriptor
·state management type
·session timeout
javax.ejb.deployment.EntityDescriptor
·list of ocntainer-managed fields
·primary key class name
描述符为实体bean和每个方法定义了事务和安全属性。这些对象的一个数组在DeploymentDescriptor中指定。
javax.ejb.deployment.ControlDescriptor
·transaction isolation level
·Method object to which this descriptor applies
·run-as mode(for odentity mapping)
·run-as identity(for identity mapping)
·transaction attribute
部署一个enterprise bean时,分配对应的描述符,然后初始化,串行化,再将其与enterprise bean类一起放入到一个jar文件中。不同厂商在定义部署描述符时可能有不同的方式。例如,一个厂商可能使用文本方式,而另一厂商可能提供图形工具。但最后结果的部署描述符是一个标准的格式,并且在不同平台间是可移植的。
EJB Jar文件
为了包装一个enterprise bean,bean的类,接口和串行化的部署描述符放在一个jar文件中。这个jar文件必须有一个manifest文件的入口以声明一个enterprise bean的部署描述符。
Name:AccountDD.ser
Enterprise-Bean:true
在manifest中作为enterprise bean列出的是部署描述符,而不是bean类。部署描述符除了定义enterprise bean,还提供jar文件中所有文件的完整描述。开发者不必关心EJB jar文件的创建。厂商应该提供一个工具来帮助开发者创建部署描述符,然后把所有必须的文件打包进一个jar文件中。
3.事务
CORBA OTS
EJB的事务模型与OTS类似。事实上,CORBA兼容的EJB服务器必须提供一个OTS兼容的事务服务。理解OTS如何工作有助于理解EJB中事务的工作方式。
定义事务
一个事务正式地定义了一个原子工作单位。一个事务中可以包含多个操作,当事务终止时,所有执行的操作或者完全执行或者完全废弃。这称为提交和回滚。
在数据库应用中广泛地使用事务。好的数据库产品对事务提供很强的支持。一个事务中访问的记录在整个事务持续期间保持锁定状态。基于数据库产品在事务开始时可以选定不同的锁定状态。选定的锁定级别应在其它的事务中优化操作的并发访问时保证数据的完整性。网络上的事务可能是分布式的,例如客户端可能在单个事务中访问两个不同的数据库。为了支持分布式事务,大多数事务管理器(包括数据库)支持两段提交。在两段提交协议中,事务管理器在准备提交事务前首先询问所有的事务参与者的工作是否完成。这是协议的第一阶段。一旦每个参与者同意提交,第二阶段才会开始。事务管理器发送独立的提交操作的命令。
OTS中的关键部件
为了更好地理解OTS如何工作,我们需要先看看其关键部件。以下的部件可以直接地映射到EJB,而理解这些部件如何在OTS中工作能使我们更好地理解EJB中的事务。
·Control
·terminator
·Coordinator
·Resource
·Synchronization 下图示出了这些对象中定义的重要方法,以及在事务体系中这些对象的作用。
虚线框内是一个事务。事务中所有的对象都参与了这个事务,提交和回滚对所有的Resource对象都适用。 Control对象代表一个事务。从该对象我们可以得到Coordinator和Terminator 。EJB开发者不会看到Control对象,容器代表bean用Control对象来管理事务。
当一个bean方法返回且该方法在部署描述符中声明在方法返回前需提交该事务时,容器也用Terminator来提交或回滚事务。提交或回滚事务时,所有事务中的对象都会相应的提交或回滚。
Resource是包含事务状态的对象。例如,它可能是一个数据库连接。在这个对象上调用commit()会更新数据库。一个rollback()调用会恢复该事务开始时通过这个连接对数据作的任何改变。完成提交或回滚后,数据库中相应的记录就会解锁。应用的锁级别会在部署描述符中指定。这个对象的完全的方法集会显示这些对象实现了两段提交协议,使得每一个对象都有权决定整个事务是提交还是回滚。当一个事务完成时,不论是提交还是回滚,都要通知Synchronization对象。与Resource不同,它并不参与两段提交协议,所以无权表决一个事务应该提交还是回滚。在事务中它扮演一个被动的角色。 Coordinator是使这一切工作起作用的对象。Resource和Synchronization通过该对象注册到事务中。Bean不直接访问这个对象。 Transaction-aware objects that are intended for use with EJB will transparently obtain a reference to the current transaction's Coordinator to register itself.
事务和可恢复的对象
在OTS中事务和可恢复的对象不同。这个区别与EJB有关。在CORBAservice? OTS规范中详细地定义了这些类型,简单地说,可恢复对象有commit()和rollback()方法,允许事务直接地操纵它自己的状态和行为。一个事务对象没有这些方法,不能被事务影响。然而,事务对象有与其关联的事务,以使分配的可恢复对象(或Resources)与事务对象的当前事务相关联。一个enterprise bean是一个事务对象的好例子。容器代表bean维护事务。任何bean分配的可恢复对象在容器的帮助下透明地放置在事务中。Bean没有commit()或rollback()方法,因此事务不能直接操纵bean.让bean作为一个可恢复的Resource并没有什么意义,因为这使得bean开发者必须为每个bean添加额外的代码,而enterprise bean几乎没有内在状态应该影响一个外部的事务。让enterprise bean作为可恢复对象的管理者,让可恢复对象完成这个工作会更好。
注意bean在容器试图提交或回滚之前可以有权表决回滚一个事务。在EJBContext中Bean可以用Coordinator中的rollback_only()方法作为setRollBackOnly()给事务设置标志,以使事务终止时间到达时请求回滚。还可以通过SessionSynchronization接口通知一个bean有关一个事务的结果。
在部署描述符中指定事务控制
bean的部署描述符包含一个ControlDescriptor对象的数组。每个ControlDescriptor描述了与方法关联的事务控制。 Bean开发者指定bean方法中的事务控制。部署者在对方法的事务相关行为没有细致了解的情况下一般不应改变这些值。如下的六个事务控制是在ControlDescriptor类定义的整形常量。除了该类的方法,没有其它的APIs能访问它们。Bean本身不访问事务控制。 Bean本身的方法不能访问其事务属性。容器读取这些控制值来维护bean的相应的事务行为。
·TX_NOT_SUPPORTED
·TX_SUPPORTS
·TX_REQUIRED
·TX_REQUIRES_NEW
·TX_MANDATORY
·TX_BEAN+NANAGED
你可以通过厂商提供的创建部署描述符的工具来为bean设置合适的ControlDescriptor.
TX_NOT_SUPPORTED
该方法不应运行在事务上下文中。如果在一个事务中执行线程,那么这个事务将挂起直到线程从方法中返回。
TX_SUPPORTS
该方法不需要事务,运行该方法时线程可能有一个活动的事务。
TX_REQUIRED
该方法必须运行在事务中。如果线程已经有一个事务,则这个线程允许进入此方法。如果线程没有事务,则容器代表线程启动一个允许线程进入的事务,当线程返回是终止事务。一般应提交事务。如果现成调用
setRollbackOnly()方法,则容器相应地执行一个回滚。
TX-REQUIRED_NEW
不论线程是否有一个事务,容器都会在方法调用期间创建一个事务。当线程返回时,容器提交或回滚这个事务。
如果线程有一个进行中的事务,则新事务会挂起直到线程返回或方法的事务终止。
TX_MANDATORY
当调用这个方法时线程必须已经在一个事务中。如果线程没有事务,则容器会抛出一个例外。
TX_BEAN_MANAGED
这个与上述几个不同。这种方法表明容器不应在事务管理中起作用。
JTS-Java事务服务
实际上JTS不是一个事务服务--只是底层服务提供者的一层接口。JTS非常简单,由一个接口和几个例外组成。从例外列表很容易能看出它类似OTS,虽然它也可以作为其它服务的接口。对于声明事务控制方式为自管理的bean,可以通过这个接口访问事务服务。厂商也可以用它来提供对客户端划分事务的支持。
如下是UserTransaction接口的定义:
public interface javax.jts.UserTransaction {
public void begin() throws IllegalStateException;
public void commit() throws
TransactionRolledBackException,
HeuristicMixedException,
HeuristicRollbackException,
SecurityException,
IllegalStateException;
Public void rollback() throws
SecurityException,
IllegalStateException;
Public void setRollbackOnly() throws
IllegalStateException;
public void setTransactionTimeout(int seconds);
public int getStatus();
//STATUS_ACTIVE,STATUS_COMMITTING,
//STATUS_COMMITTED,STATUS_MARKED_ROLLBACK
//STATUS_NO_TRANSACTION,STATUS_PREPARED
//STATUS_PREPARING,STATUS_ROLLEDBACK
//STATUS_ROLLING_BACK,STATUS_UNKNOWN
}