权衡 Apache Geronimo EJB 事务选项,第 2 部分: Bean 管理事务
本系列分为三部分,将探索 Geronimo 和 OpenEJB 可以为您提供什么帮助,以及在 EJB 2.1 中现在可以实现的 EJB 事务概念(让您顺利进入 EJB 3.0)。第 2 部分(即本文)将详细描述 EJB bean 管理的事务,研究可以生成的两种 bean 管理的事务(Java Transaction API (JTA) 和 Java Database Connectivity (JDBC) 事务)的用法。您将了解这些事务的回滚和如何在 Geronimo 中使用 bean 管理的事务。
现在简要回顾一下第 1 部分中涵盖的内容:使用 EJB 事务,您可以从下列两个选项中进行选择:
- 您可以将事务实现委托给 EJB 容器。这就是容器管理的事务,也是本系列文章第 1 部分的焦点。请参阅 第 1 部分,获得对事务和容器管理的事务的简介。
- 在提交事务和将其回滚时,允许通过编程方式管理企业 bean。应用程序开发人员必须明确将事务逻辑编入企业 bean 代码来实现它。这就是 bean 管理的事务,是本系列的第 2 部分(也就是本文)的焦点。
在第 3 部分中,您将了解与容器管理的事务和 bean 管理的事务有关的难题和附加特性。
与 bean 管理的事务相比较,容器管理的事务更简单且代码更少。但是在使用容器管理的事务时存在以下情况:您的企业 bean 方法既可以参与到事务中,也可以不参与。如果您需要更粗略的逻辑,并在基于特定有效性逻辑的情况下使用粗略逻辑提交事务或回滚事务,那么您应该使用 bean 管理的事务。bean 管理的事务可使您对事务边界进行全面控制。
会话 bean 或消息驱动 bean (MDB) 可以使用 bean 管理的事务。实体 bean 不能使用 bean 管理的事务。这是因为 EJB 容器控制了加载或存储实体 bean 的数据的时间。
在 bean 管理的事务中,容器必须允许 bean 实例在一个方法中连续执行几个事务,但是要记住,不能执行嵌套事务。如果您试图启动一个新事务而 bean 实例还没有提交前一个事务,那么容器将抛出一个异常。
您可以使用两种类型的 bean 管理的事务:
- JTA 事务
- JDBC 事务
我们将在下一节中查看这两种事务。
JTA 是事务管理器和分布式事务处理系统所涉及的其他组件之间的一个接口规范。通过使用接口,不必使用事务管理器的特有 API 就可以单独地划分事务。
所有 EJB 容器都必须支持 JTA API。对于一名开发人员,这允许您将事务指令传达给 EJB 容器,并以通用、简单的方式提供事务指令。此方法使您不必担心事务服务的底层工作。
javax.transaction.UserTransaction
使用 bean 管理的 JTA 事务时,可使用 javax.transaction.UserTransaction 接口来划分事务。在该接口上,有三个有趣的方法:
begin()—— 创建一个新的事务,并将该事务与当前线程关联起来。commit()—— 提交与当前线程有关联的事务。rollback()—— 强行回滚与当前线程有关联的事务。
在 begin() 和 commit() 之间发生的所有更新都是在一个事务中完成的。
UserTransaction 接口上的其他方法包括:
getStatus()—— 检索与当前线程有关的事务的状态。返回值是javax.transaction.Status接口上定义的常数。setRollbackOnly()—— 强行使事务终止。setTransactionTimeout(int)—— 设置事务中止前能运行的最大次数。这在避免死锁时很有用。
请参阅本文的 参考资料 部分,获得 Sun 公司的 JavaDocs 中有关 UserTransaction 和 UserStatus 接口的链接。
如何使用 bean 方法获得 UserTransaction 的最初引用呢?基于企业 bean 的类型,您可从 bean 上下文中获得接口:
- 对于会话 bean,可从
javax.ejb.EJBContext调用getUserTransaction()。 - 对于 MDB,可从
MessageDrivenContext.getUserTransaction()调用getUserTransaction()。
您也可以通过 JNDI 检索接口。清单 1 显示了一个例子。
清单 1. 如何通过 JNDI 获取 UserTransaction 接口
public someMethodOnMyBean()
{
Context initCtx = new InitialContext();
UserTransaction utx = (UserTransaction)initCtx.lookup(
"java:comp/UserTransaction");
utx.begin();

utx.commit();
}

}
通常,在同一个方法中启动事务并提交该事务是一个好主意。这有助于您跟踪事务开始和结束的地方。同样,要使事务开放的时间尽可能的短。事务需要系统资源,因此如果保持事务长时间的开放,可能会影响多用户性能。
使用 JTA 事务的最大好处是它允许您跨越多个不同数据库的多个更新。但要记住,JTA 实现不支持嵌套事务。
清单 2 显示了会话 bean 更新两个不同数据库的例子。数据源是通过 JNDI 进行检索的。您可以像往常一样检索数据库连接并准备语句。
(注意,我在演示 JTA 事务的基本实现(用粗体显示)。为了实现这一点,我将用顶级方法来显示所有代码。不要尝试使用任何重用形式的普通抽象。)
public class MySessionEJB implements SessionBean {
EJBContext ejbContext;
public void updateTwoDatabases(
) {
DataSource dbOneDataSource = null;
DataSource dbTwoDataSource = null;
Connection dbOneConnection = null;
Connection dbTwoConnection= null;
Statement dbOneStatement = null;
Statement dbTwoStatement = null;
String sqlUpdateDbOne = null;
String sqlUpdateDbTwo = null;
javax.transaction.UserTransaction ut = null;
try {
InitialContext initCtx = new InitialContext();
// retrieve our first Connection and
// prepare a statement
dbOneDataSource = (javax.sql.DataSource)
initCtx.lookup("java:comp/env/jdbc/dbOneDS");
dbOneConnection = dbOneDataSource.getConnection();
// setup SQL here,
// perhaps we're using parameterized queries
sqlUpdateDbOne = 
dbOneStatement =
dbOneConnection.prepareStatement(sqlUpdateDbOne);
// retrieve our second Connection and
// prepare a statement
dbTwoDataSource = (javax.sql.DataSource)
initCtx.lookup("java:comp/env/jdbc/dbTwoDS");
dbTwoConnection = dbTwoDataSource.getConnection();
// setup SQL here,
// perhaps we're using parameterized queries
sqlUpdateDbTwo = 
dbTwoStatement =
dbTwoConnection.prepareStatement(sqlUpdateDbTwo);
// Now setup a JTA transaction to encompass both
// database updates //
ut = ejbContext.getUserTransaction();
// Start our transaction here
ut.begin();
dbOneStatement.executeUpdate();
dbTwoStatement.executeUpdate();
// commit the transaction
ut.commit();
// cleanup
dbOneStatement.close();
dbTwoStatement.close();
dbOneConnection.close();
dbTwoConnection.close();
}
catch (Exception e) {
//something went wrong
try {
//rollback the exception
ut.rollback();
} catch (SystemException se) {
//Rollback has failed!
throw new EJBException
("Attempted to rollback, but it failed: "
+ se.getMessage());
}
//Lastly throw an exception to report what went wrong
throw new EJBException
("Transaction failed: " + ex.getMessage());
}
}如您所见,JTA bean 管理的事务的基本实现非常简单。在事务启动、提交和回滚时,所有您要做的就是明确控制事务。
如果您过去曾经编写过 JDBC 代码,那么实现 JDBC 事务对于您来说应该很熟悉。只有在具有现有的 JDBC 代码,并且必须在企业 bean 实现中使用它时,才使用 JDBC 事务,不过,这被认为是传统处理方式。让我们更近一步了解这类事务。
在执行一个正常地 JDBC 更新时,所有 SQL 语句都被自动提交。为了自己控制事务边界,在使用前可通过在 Connection 对象上发布 setAutoCommmit(false) 语句将该功能关闭。如果需要更好控制 SQL 执行,请使用该选项。
由 java.sql.Connection 接口提供 commit 和 rollback 方法,并由数据库的事务管理器(而不是 EJB 容器)来控制 JDBC 事务。下面的 清单 3 阐明了这一点,显示了一个虚构的会话 bean 方法,该方法使用标准 JDBC 预备语句更新数据库中客户的名字。
注意,这里没有抽象。所有代码都是使用顶级 bean 编写的。不能使用重用和零委托。请不要将这作为一种方法使用。记住,我将演示如何声明事务边界,以便更好地实现 JDBC 遗留代码的事务控制。
清单 3. JDBC 事务的用法
public void updateFirstName (int customerId, String firstName) {
try {
//getConnection creates a JDBC connection for us
Connection con = getConnection();
con.setAutoCommit(false);
String sql = "update customer set firstName = ? where customerId = ?";
PreparedStatement updateCustomerName = con.prepareStatement(sql);
updateCustomerName.setString(1,firstName);
updateCustomerName.setInt(2,customerId);
updateCustomerName.executeUpdate();
con.commit();
} catch (Exception ex) {
try {
con.rollback();
throw new EJBException("Customer name update
failed: " + ex.getMessage());
} catch (SQLException sqx) {
throw new EJBException("Rollback failed: " +
sqx.getMessage());
}
}
}因此可以用来封装遗留 JDBC 代码的常用方法是:
- 在连接对象上将
setAutoCommit属性设置为 false。 - 在执行 SQL 之后调用连接上的 commit 方法。
- 如果出现任何错误,则调用连接上的 rollback 方法。
此外,JDBC 事务不是实现 bean 管理的事务的首选方式。如果不需要重用现有 JDBC 代码,则应该考虑使用 JTA 事务。
因为由您来控制事务边界,所以当提交或回滚 bean 管理的事务时,有些特定的规则您必须知道:
- 在从业务方法中返回或发生
ejbTimeout之前,无状态会话 bean 必须提交或回滚事务。容器可能会检测事务启动时的情况,但是不会提交事务。在这种情况下,容器将回滚事务并抛出一个异常。 - 在从业务方法中返回之前,有状态会话 bean 不必提交或回滚事务。这是因为事务可以跨越几个客户端调用。有状态会话 bean 表示了会话状态。EJB 容器将等待会话 bean 实例提交或回滚事务。
- 在消息监听器方法或
ejbTimeout方法返回之前,MDB 必须提交事务。在这种情况下,容器将回滚事务并抛出一个异常。
注意,对于 JTA 事务,如果数据库在多个调用之间打开或关闭数据库连接,则仍将保留事务。但是,对于 JDBC 事务,将不保留事务。
在处于事务中时,不要调用 java.sql.Connection 或 javax.jms.Session 接口的 commit() 或 rollback()。
同样,不要调用 EJBContext 接口的 getRollBackOnly() 和 setRollBackOnly()。容器将抛出一个异常,原因是:
- 您可以通过调用
javax.transaction.UserTransaction的getStatus()方法来获得事务的状态。这等同于调用getRollBackOnly。 - 可以使用
javax.transaction.UserTransaction接口的rollback()方法来回滚事务。这等同于调用setRollbackonly()。
要与 Geronimo 一起使用 bean 管理的事务时,没有很多配置工作需要您做!因为事务边界是通过编程控制的,您要像本文前一小节中所显示的那样,在代码中处理大部分工作。
OpenEJB 是 Geronimo 的 EJB 容器实现。您只需要负责配置好 OpenEJB 来使用 bean 管理的事务即可。可以用部署描述符对每个企业 bean 进行配置。
要使用 bean 管理的事务,则需要使用 EJB 部署描述符中的 <transaction-type> 元素来指定值 Bean。
您可以使用 XDoclet 来生成实现和使用 EJB 框架所需的编程工件中更单调的方面。这包括 EJB 部署描述符。因此要指定 bean 管理的事务,则需要使用 XDoclet 的类似 JavaDoc 的标识语言。
要为每个企业 bean 指示 bean 管理的事务,可以使用 XDoclet 标识来设置 @ejb.bean transaction-type="Bean"。
清单 4 显示了一个会话 bean,它使用 XDoclet 来声明事务类型标识(粗体显示)。
清单 4. bean 管理的会话 bean 的 XDoclet 标识
package org.my.package.ejb;
/**
* Sample session bean.
* Declare all my XDoclet tags here
* 
*
* @ejb.bean name="SampleSession"
* type="Stateless"
* local-jndi-name="java:comp/env/ejb/SampleSessionLocal"
* jndi-name="org.my.package.ejb/SampleSessionLocal/Home"
* view-type="both"
* transaction-type="Bean"
* 
* 
*/
public abstract class SampleSessionBean implements javax.ejb.SessionBean {

}注意:用于指定容器管理的事务属性的 XDoclet 标记与上述标记非常相似,因此不会被迷惑:
- 容器管理的事务
@ejb.transaction type="Required" - bean 管理的事务
@ejb.bean transaction-type="Bean"
@ejb.bean 标签,将使用默认事务边界(这是容器管理的事务)。
清单 5 是名为 SampleSession 的无状态会话 bean 生成 ejbjar.xml 的清单示例。在 Java 代码中指定了 @ejb.bean transaction-type="Bean"。只需要注意 <transaction-type> 属性(粗体)即可!

<ejb-jar >
<description><![CDATA[No Description.]]></description>
<display-name>Generated by XDoclet</display-name>
<enterprise-beans>
<!-- Session Beans -->
<session >
<description><![CDATA[Sample session
bean.]]></description>
<ejb-name>SampleSession</ejb-name>
<home>org.my.package.ejb.SampleSessionHome</home>
<remote>org.my.package.ejb.SampleSession</remote>
<local-home>org.my.package.ejb.SampleSessionLocalHome
</local-home>
<local>org.my.package.ejb.SampleSessionLocal
</local>
<ejb-class>org.my.package.ejb.SampleSessionSessionSession
</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Bean</transaction-type>
</session>



浙公网安备 33010602011771号