我们知道事务是一组不可分割的操作,事务中的操作都必须成功执行才可以提交,否则,事务要退回到执行前的状态。当事务提交时,修改的数据被保存起来。如果事务中的一个操作失败,则事务需要回滚,消除已执行操作造成的影响,保持数据的完整性。事务通常具有下面的代码格式:
begin transaction
//事务操作
SQL语句
……
//或 rollback transaction
在J2EE环境中,事务分为容器管理的事务和Bean管理的事务。下面分别介绍一下这两种事务管理。
容器管理的事务
在使用容器管理的事务中,容器用来设置事务边界,即指定事务的开始和结束。容器管理的事务可以使用在任何一类的企业Bean中。这类事务管理简化了开发过程,因为事务管理代码是隐含的,不需要开发人员提供。一般来讲,在企业Bean开始一个方法前由容器开始一个事务,在方法结束前由容器提交事务。每个方法都同一个事务联系在一起。在一个方法中不允许嵌套的事务和多事务出现。容器管理的事务并不要求每个方法都同事务联系在一起。
当部署一个企业Bean时,你可以通过设置企业Bean属性把企业Bean的方法同事务联系在一起。如在企业Bean的部署过程中,可在企业Bean的事务选项页内选择事务的类型和方法的事务属性,如图12-1所示。图中选择的事务管理类型为容器管理类型,远程接口中每个方法的事务属性(Transaction Attribute)都是required,说明执行每个方法前都要求开始一个事务。
Ø 事务属性
事务的属性控制着事务的执行范围,图12-2说明事务的执行范围对方法的影响。在这个图中,Bean1的方法A开始一个事务,然后调用Bean2的方法B。方法B是执行在方法A的事务范围内,还是开始一个新的事务?在容器管理的事务中,方法B同哪一个事务相关取决于方法B的事务属性设置。
事务属性具有下列的值:
l Required
l RequiresNew
l Mandatory
l NotSupported
l Supports
l Never
下面是这些属性的说明:
Required:如果客户已经在一个事务中运行,那么容器调用企业Bean方法时将在客户开始的事务中运行这个方法,而不是为这个方法创建一个新的事务。否则,容器将为这个方法开始一个新的事务。Required属性一般用作默认的事务属性,同大多数方法能很好工作。如果不确定要为方法选择哪个属性时,Required是一个选择。在开发初期可以选择这个属性,后期根据需要再做调整。
RequiresNew:说明企业Bean的方法需要一个新的事务。有两种情况:
1. 如果客户没有在事务中运行,那么容器在执行方法前为这个方法开始一个新的事务。
2. 如果客户在事务中运行,那么容器按照下列步骤为方法开始一个新的事务。
l 容器挂起客户当前的事务。
l 为这个方法创建一个新的事务。
l 在新事务中调用这个方法。
l 方法执行结束后,提交新事务并继续运行挂起的客户事务
Mandatory:如果客户运行在一个事务中并调用企业Bean的方法,这个方法将在客户事务中运行。如果客户没有运行在事务中,容器将产生TransactionRequiredException异常。如果要求方法必须在客户事务中运行,那么使用mandatory事务属性。容器不为同mandatory事务属性关联的方法创建新事务。
NotSupported:如果客户运行在事务中,容器将挂起客户的事务,然后执行这个方法。方法执行结束后,容器恢复执行挂起的事务。如果客户不在一个事务中运行,那么容器不为该方法创建事务。对不需要事务支持的方法使用这个属性会提高系统的性能,因为减少了事务开销。
Supports:如果运行在事务中的客户调用这个方法时,方法将在客户的事务中运行。如果不运行在事务中的客户调用该方法时,容器不为这个方法开始新的事务。这个事务属性指示容器根据客户的情况执行方法。
Never:这个事务属性保证方法不在任何事务中运行。如果客户不运行在事务中,容器执行这个方法。如果客户运行在事务中并调用这个方法时,将抛出RemoteException异常。这个事务属性减少了系统的事务开销,在允许的条件下可以增强系统的性能。
我们可以使用表格12-1进一步说明事务属性的设置对企业Bean方法的作用。表中的T1和T2表示事务,由容器管理。T1表示客户事务,T2表示容器在执行企业Bean方法前为其创建的新事务。事务T1、T2可以同一个企业Bean相关联,但多数情况下是同两个企业Bean相关联,如图12-1所示的情形。
Ø 属性设置
由于事务属性存储在部署描述符中,可以在J2EE应用程序的不同开发阶段中设置它,如在企业Bean的创建阶段、程序的安装阶段、程序的部署阶段。事务的属性一般应该由程序的开发人员设置修改,不该由应用程序的部署人员设置,因为他们不是很了解组件方法的具体设计及事务对这些方法的影响。
事务属性的设置可以针对整个企业Bean,也可以针对某个单一的方法。如果对企业Bean和它的方法说明不同的事务属性,那么采用对方法设置的属性,方法的事务属性具有优先权。当对方法设置事务属性时,不同的企业Bean类型会有不同的要求。会话Bean的业务方法可以具有事务属性,但不能对其create方法设置事务属性。实体Bean的业务方法、create方法、remove方法、查询方法可以具有事务属性。消息驱动Bean的onMessage方法可以具有Required或NotSupported事务属性。
Ø 事务回滚
有两种方法回滚容器管理的事务。一种是当程序产生系统异常时,容器可以自动回滚事务;另一种是通过调用javax.ejb.EJBContext接口的setRollbackOnly方法指示容器回滚事务。在第二种方法中,当企业Bean产生异常时,事务回滚不是自动进行的,而是通过调用setRollbackOnly方法开始事务回滚。
下面的bookSale_CMT方法说明一种图书购买操作。例子的详细代码见本章后面的程序实例。在本例中为了说明事务的提交和回滚,首先用图书的库存量减去购买量,然后用这个值(checkingBook)修改数据库中的图书库存量。数据库修改后再检查checkingBook的正负。如果为正说明库存量大于购买数量,购买交易会成功,最后调用updateTotal方法把销售图书获得的资金存入资金表中。如果checkingBook为负说明图书库存量小于购买数量,购买交易不能完成,则调用setRollbackOnly方法回滚updateBooks方法对数据库的修改并抛出图书库存量不足的异常。如果updateBooks方法或updateTotal方法操作失败,将产生SQLException系统异常,这将引起容器自动回滚同bookSale_CMT方法相关联的事务。
public void bookSale_CMT(int amount) throws
InsufficientBookException {
try {
checkingBook = checkingBook - amount;
savingTotal = savingTotal + bookPrice*amount;
updateBooks(checkingBook);
if (checkingBook < 0) {
//事务回滚
context.setRollbackOnly();
throw new InsufficientBookException("图书库存不足");
}
updateTotal (savingTotal);
} catch (SQLException ex) {
throw new EJBException
("Transaction failed due to SQLException: " + ex.getMessage());
}
}
Ø 会话Bean中变量的同步
在实体Bean中,容器回滚事务除了恢复SQL语句对数据库的修改外,还可以恢复对实例变量的修改。做到这一点是通过容器自动调用实体Bean的ejbload方法实现的。在这个方法中可以加入从数据库中恢复变量值的代码,我们将在下一章中解释这个方法的使用。
会话Bean中的变量(如checkingBook、savingTotal)在容器回滚事务时不会自动恢复,必须重新设置。一种简单的恢复会话Bean中变量的方法是在有状态会话Bean中装配SessionSynchronization接口。 通过这个接口会话Bean可以获得事务执行的边界点。SessionSynchronization接口提供个下面的方法:
l afterBegin
l beforeCompletion
l afterCompletion
这些方法由容器调用,在事务的边界点执行。容器在开始一个事务,执行业务方法前调用afterBegin方法。可以在这个方法中设置会话Bean业务方法需要的变量,如可在这个方法中设置checkingBook、bookPrice、和savingTotal变量。这些变量值可从数据库中获得,如下面的afterBegin代码:
public void afterBegin() {
System.out.println("afterBegin()");
try {
checkingBook = selectBookInStock(bookname);
savingTotal = selectTotal();
bookPrice = selectBookPrice(bookname);
} catch (SQLException ex) {
throw new EJBException("afterBegin Exception: " + ex.getMessage());
}
}
容器在业务方法执行结束后,而在提交事务前调用beforeCompletion方法。在这个方法中可以调用setRollbackOnly方法回滚事务。如可以把bookSale_CMT方法中的if语句代码放在这个方法中。如果库存量不足,开始事务回滚。如下面的beforeCompletion代码:
public void beforeCompletion(){
……
if (checkingBook < 0) {
context.setRollbackOnly();
}
……
}
最后容器调用afterCompletion方法通知会话Bean事务结束。它有一个boolean参数。如果事务提交,值为true;事务回滚,则为false。如果事务回滚,可以在这个方法中用回滚后的数据库中的数据刷新会话Bean实例中的变量。如下面的afterCompletion代码:
public void afterCompletion(boolean committed) {
System.out.println("afterCompletion: " + committed);
if (committed == false) {
try {
checkingBook = selectBookInStock(bookname);
savingTotal = selectTotal();
} catch (SQLException ex) {
throw new EJBException("afterCompletion SQLException: "
+ ex.getMessage());
}
}
}
Ø 调用方法限制
在容器管理的事务中,有一些接口中的方法不能使用,否则会造成容器事务管理上的冲突。表12-2中所示的接口方法不能用在容器管理的事务方法中。
表12-2
接口 | 容器管理的事务中禁用的方法 |
Java.sql.Connection | commit、setAutoCommit、rollback |
Javax.jms.Session | commit、rollback |
Javax.transaction.UserTransactin | 容器管理的事务中禁止这个接口的所有方法 |
javax.ejb.EJBContext | getUserTransaction |
Bean管理的事务
容器管理的事务易于实现,减少了事务管理上的编码。但它缺少对事务管理的精确控制。在实体Bean中只能使用容器管理的事务。容器管理的事务中每个方法只能同一个事务相关。Bean管理的事务使开发人员能够使用嵌套事务,即在一个事务中可以包含另一个事务,使开发人员可以很好地控制事务流程。如下面的说明性代码:
begin transaction_1
...
修改table1
...
if (条件A)
commit transaction_1
else if (条件B)
修改 table2
commit transaction_1
else
rollback transaction_1
begin transaction_2
修改 table3
commit transaction_2
从上面的代码中可以看到,在Bean管理的事务中可以根据条件修改不同的表并决定何种条件提交并开始一个新的事务。
当使用bean管理的事务时,必须决定是使用JDBC事务还是使用JTA事务。
l JDBC事务:我们在上一章中已经介绍过,是由特定数据库管理系统的事务管理机制控制的事务。JDBC事务边界是通过使用javax.sql.Connection接口定义的commit方法和rollback方法来确定的。
l JTA(Java Transaction APl)事务:使用Java事务接口创建的事务。JTA事务受到J2EE事务管理机制的控制,独立于任何特定的数据库系统。JTA事务边界是通过使用javax.transaction.UserTransaction接口的begin、commit和rollback方法确定的。JTA的不足是:它不支持嵌套的事务。
使用JTA完成前面的图书购买方法将有如下的代码:
public void bookSale_BMT(int amount) throws
InsufficientBookException {
UserTransaction ut = context.getUserTransaction();
try {
//开始事务
ut.begin();
//获得书的价格
bookPrice = selectBookPrice(bookname);
//计算销后金额
savingTotal = selectTotal()+bookPrice*amount;
//计算售后库存量
checkingBook = selectBookInStock(bookname)-amount;
//修改库存量
updateBooks(checkingBook);
if (checkingBook < 0) {
//抛出库存不足异常
throw new InsufficientBookException();
}
updateTotal(savingTotal);
//提交事务
ut.commit();
}
catch (InsufficientBookException ex) {
try {
//库存不足,回滚事务
ut.rollback();
throw new InsufficientBookException("库存量不足");
} catch (SystemException syex) {
throw new EJBException
("Rollback failed: " + syex.getMessage());
}
}
……
}
从上面的代码中可以看到,数据库的操作代码放在ut.begin()和ut.commit()之间。如果库存量不足,将抛出InsufficientBookException异常并执行ut.rollback()开始事务回滚。这个方法的使用见本章后面的程序实例说明。
在Bean管理的事务中,javax.ejb.EJBContext接口中的setRollbackOnly和getRollbackOnly方法不能用在Bean管理的事务方法中。
企业Bean的事务选择
企业Bean既可以使用容器管理的事务,也可以使用Bean管理的事务。使用容器管理的事务在多数情况下会很好的工作。表12-3列出了企业Bean允许使用的事务类型。
从表中可以看到实体Bean只能使用容器管理的事务。会话Bean和消息驱动Bean则可以使用任一类型的事务管理。容器管理的事务属性在部署时设定,它的事务回滚可以通过在代码中调用EJBContext接口的setRollbackOnly方法完成,或在发生系统级异常时由容器自动完成。Bean管理的事务异常通过调用事务的回滚方法完成。容器管理的事务简单,代码量少,但缺少精确控制。Bean管理的事务需要较多的事务管理代码,但可对事务流程有很好的控制。
事务超时限定
对于容器管理的事务,可以通过在文件domain.xml(在J2EE应用服务器的config子目录中) 中设置timeout-in-seconds 属性控制事务的执行时间,事务执行时间超过这个值将自动回滚。例如设置:
timeout-in-seconds=5
如果事务在5秒内没有完成,容器将自动回滚事务。当J2EE服务器首次安装时,超时限定设置为0,意味着没有限定事务的执行时间。只有容器管理的事务受超时限定属性的影响。对于Bean管理的JTA事务可以通过调用UserTransaction接口的setTransactionTimeout方法设置事务的执行时间。
多数据库修改
在J2EE环境下,除Bean管理的JDBC事务外, J2EE的事务管理器控制着所有企业Bean的事务。J2EE事务管理器允许一个企业 Bean在一个事务中修改多个数据库。下面的两个图说明在一个单一的事务中修改多个数据库的情况。在图12-3中,客户在Bean-A中调用一个业务方法。业务方法开始一个事务,修改数据库X和Y,并调用Bean-B中的一个业务方法。第二个业务方法修改数据库Z并把控制返回给Bean-A中的业务方法,由Bean-A中的方法提交事务。所有的数据库修改在一个事务中完成。
图12-3
在图12-4中,客户调用Bean-A中的一个方法,开始一个事务并修改数据库X。然后Bean-A 调用Bean-B中的一个方法,这个方法在远端的J2EE服务器上执行。Bean-B中的方法修改数据库Y。两个J2EE服务器上的事务管理器将保证两个数据库的更新在一个事务中完成。
图12-4
程序实例
为了说明事务处理,下面我们举一个简单的图书销售中的事务例子。这个例子用到下面的两个数据表:图书库存表和销售资金表。为了简化程序编码,图书库存表名为books,具有下列结构和数据:
bookname price amount
(char 10) (double) (integer)
-------------------------------
a 20 100
b 12.5 200
销售资金表名为bookfund,只有一行和一列。存储销售图书的总金额,结构和数据如下所示:
total
(double)
------
0
用户使用下面的HTML界面输入购买的图书名称和数量。
Ø HTML界面代码
HTML代码为:
u 文件名:bookdeal.html
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<HTML>
<HEAD>
<TITLE>
购买图书
</TITLE>
</HEAD>
<BODY BGCOLOR="WHITE">
<BLOCKQUOTE>
<H3>购买图书</H3>
<FORM METHOD="GET" ACTION="BookDeal">
<P>
输入书名:
<INPUT TYPE="TEXT" NAME="bookname"/>
<P>
购买数量:
<INPUT TYPE="TEXT" NAME="quantity"/>
<P>
<INPUT TYPE="SUBMIT" NAME="buy" VALUE="购买"/>
<INPUT TYPE="RESET" VALUE="重置"/>
</FORM>
</BLOCKQUOTE>
</BODY>
</HTML>
把上面代码文件保存在D:/j2ee_apps/chapter12目录中。
Ø 会话Bean代码
我们使用会话Bean中的方法实现图书的销售逻辑。若购买成功,图书库存量将减少,销售金额将增加。若购买失败,说明库存量不足,数据库中的数据应将保持不变。我们用事务管理保证数据的完整性。会话Bean代码如下:
HOME接口
u 文件名:BookHome.java
package book;
import java.rmi.RemoteException;
import javax.ejb.*;
public interface BookHome extends EJBHome {
public Book create(String bookId)
throws RemoteException, CreateException;
}
把上面代码文件保存在D:/j2ee_apps/chapter12目录中。
远程接口
u 文件名:Book.java
package book;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import javax.ejb.*;
public interface Book extends EJBObject {
public void bookSale_CMT(int amount)
throws RemoteException, InsufficientBookException;
public void bookSale_BMT(int amount)
throws RemoteException, InsufficientBookException;
public int getBookInStock()
throws RemoteException;
public double getTotal()
throws RemoteException;
}
把上面代码文件保存在D:/j2ee_apps/chapter12目录中。
Bean类
Bean类代码包括实现销售业务逻辑及数据库操作的一些方法,其中bookSale_CMT是容器管理的销售方法,bookSale_BMT是Bean管理的销售方法。Bean类中有的方法在调用bookSale_CMT时会用到,而调用bookSale_BMT时则不需要。结合程序中的代码注释,了解代码完成的功能。
u 文件名:BookBean.java
package book;
import java.util.*;
import javax.ejb.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import book.*;
import javax.transaction.UserTransaction;
import javax.transaction.SystemException;
public class BookBean implements SessionBean, SessionSynchronization {
private String bookname;
private int checkingBook;
private double savingTotal;
private double bookPrice;
private SessionContext context;
private Connection con;
private static final String dbName = "java:comp/env/jdbc/BookDB";
//销售方法,在这个方法中给出销售过程的具体操作,
//所有操作在容器管理事务中进行
public void bookSale_CMT(int amount) throws
InsufficientBookException {
try {
checkingBook = checkingBook - amount;
savingTotal = savingTotal+bookPrice*amount;
updateBooks(checkingBook);
if (checkingBook < 0) {
//事务回滚
context.setRollbackOnly();
throw new InsufficientBookException("图书库存不足");
}
updateTotal(savingTotal);
} catch (SQLException ex) {
throw new EJBException
("Transaction failed due to SQLException: "
+ ex.getMessage());
}
}
//销售方法,在这个方法中给出销售过程的具体操作,
//所有操作在Bean管理事务中进行
public void bookSale_BMT(int amount) throws
InsufficientBookException {
UserTransaction ut = context.getUserTransaction();
try {
//开始事务
ut.begin();
//获得书的价格
bookPrice = selectBookPrice(bookname);
//计算销后金额
savingTotal = selectTotal()+bookPrice*amount;
//计算售后库存量
checkingBook = selectBookInStock(bookname)-amount;
//修改库存量
updateBooks(checkingBook);
if (checkingBook < 0) {
//抛出库存不足异常
throw new InsufficientBookException();
}
updateTotal(savingTotal);
//提交事务
ut.commit();
}
catch (InsufficientBookException ex) {
try {
//库存不足,回滚事务
ut.rollback();
throw new InsufficientBookException("库存量不足");
} catch (SystemException syex) {
throw new EJBException
("Rollback failed: " + syex.getMessage());
}
}
catch (Exception ex) {
try {
//其他异常,回滚事务
ut.rollback();
throw new EJBException ("异常导致事务回滚: " +
ex.getMessage());
} catch (SystemException syex) {
throw new EJBException
("Rollback failed: " + syex.getMessage());
}
}
}
//远程接口中的方法,使用它获得库存量
public int getBookInStock() {
return checkingBook;
}
//远程接口中的方法,使用它获得销售金额
public double getTotal() {
return savingTotal;
}
//创建book实例
public void ejbCreate(String bookId) throws CreateException {
bookname = bookId;
}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext context) {
this.context = context;
}
//在容器管理的事务中,执行业务方法前由容器调用
public void afterBegin() {
System.out.println("afterBegin()");
try {
checkingBook = selectBookInStock(bookname);
savingTotal = selectTotal();
bookPrice = selectBookPrice(bookname);
} catch (SQLException ex) {
throw new EJBException("afterBegin Exception: " + ex.getMessage());
}
}
//在容器管理的事务中,业务方法结束后,事务提交前由容器调用
//bookSale_CMT方法中对setRollbackOnly()的调用可放在这个
//方法中,用于容器管理的事务回滚
public void beforeCompletion(){
System.out.println("beforeCompletion()");
//if (checkingBook < 0) {
// context.setRollbackOnly();
//}
}
//在容器管理的事务中,通知会话Bean事务结束
public void afterCompletion(boolean committed) {
System.out.println("afterCompletion: " + committed);
if (committed == false) {
try {
checkingBook = selectBookInStock(bookname);
savingTotal = selectTotal();
} catch (SQLException ex) {
throw new EJBException("afterCompletion SQLException: "
+ ex.getMessage());
}
}
}
//--------------以下为数据库操作方法--------------------
//建立数据库连接
private void makeConnection() {
try {
InitialContext ic = new InitialContext();
DataSource ds = (DataSource) ic.lookup(dbName);
con = ds.getConnection();
} catch (Exception ex) {
throw new EJBException("Unable to connect to database. "
+ ex.getMessage());
}
}
//释放连接
private void releaseConnection() {
try {
con.close();
} catch (SQLException ex) {
throw new EJBException("releaseConnection: " + ex.getMessage());
}
}
//修改图书存量,数据库表名为books
private void updateBooks(int amount) throws SQLException {
makeConnection();
String updateStatement = "update books set amount = ? "
+ "where bookname = ?";
PreparedStatement prepStmt = con.prepareStatement(updateStatement);
prepStmt.setInt(1, amount);
prepStmt.setString(2, bookname);
prepStmt.executeUpdate();
prepStmt.close();
releaseConnection();
}
//修改图书销售金额,数据库表名为bookfund
private void updateTotal(double amount) throws SQLException {
makeConnection();
String updateStatement = "update bookfund set total = ? ";
PreparedStatement prepStmt = con.prepareStatement(updateStatement);
prepStmt.setDouble(1, amount);
prepStmt.executeUpdate();
prepStmt.close();
releaseConnection();
}
//从数据库中获得购买图书存量,数据库表名为books
private int selectBookInStock(String bookId) throws SQLException {
makeConnection();
String selectStatement = "select amount "
+ "from books where bookname = ? ";
PreparedStatement prepStmt = con.prepareStatement(selectStatement);
prepStmt.setString(1, bookId);
ResultSet rs = prepStmt.executeQuery();
if (rs.next()) {
int result = rs.getInt(1);
prepStmt.close();
releaseConnection();
return result;
} else {
prepStmt.close();
releaseConnection();
throw new EJBException(bookId + " not found.");
}
}
//从数据库中获得购买图书价格,数据库表名为books
private double selectBookPrice(String bookId) throws SQLException {
makeConnection();
String selectStatement = "select price "
+ "from books where bookname = ? ";
PreparedStatement prepStmt = con.prepareStatement(selectStatement);
prepStmt.setString(1, bookId);
ResultSet rs = prepStmt.executeQuery();
if (rs.next()) {
double result = rs.getDouble(1);
prepStmt.close();
releaseConnection();
return result;
} else {
prepStmt.close();
releaseConnection();
throw new EJBException(bookId + " not found.");
}
}
//从数据库中获得图书销售金额,数据库表名为bookfund
private double selectTotal() throws SQLException {
makeConnection();
String selectStatement = "select total from bookfund";
PreparedStatement prepStmt = con.prepareStatement(selectStatement);
ResultSet rs = prepStmt.executeQuery();
if (rs.next()) {
double result = rs.getDouble(1);
prepStmt.close();
releaseConnection();
return result;
} else {
prepStmt.close();
releaseConnection();
throw new EJBException("table maybe empty.");
}
}
}
把上面代码文件保存在D:/j2ee_apps/chapter12目录中。
从代码可以看出,本例图书销售的逻辑较简单。事务处理主要涉及图书的库存量。如果库存量不足,应该回滚事务,这是因为在例子中首先修改了图书库存量,然后判断图书库存量是否小于0,小于0说明库存不足,应停止交易。停止交易应把数据恢复到交易前的状态。
在上面的会话Bean代码中,我们给出了两个售书业务方法:bookSale_CMT和bookSale_BMT。第一个方法是容器管理的事务方法,第二个是Bean管理的事务方法,在程序中只能调用一个。我们将在Servlet中使用这两个方法中的一个。
Ø Servlet代码
u 文件名:BookServlet.java
用户的购书请求通过HTML网页把用户数据传给Servlet。由Servlet调用会话Bean的bookSale_CMT或bookSale_BMT方法完成销售操作。Servlet代码如下:
package book;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import java.rmi.RemoteException;
import book.*;
public class BookServlet extends HttpServlet {
BookHome bookHome;
Book book;
int bookQuantity;
public void init(ServletConfig config) throws ServletException {
System.out.println("BookServlet: init()");
try {
Context context = new InitialContext();
Object homeObject = context.lookup("BookHome");
bookHome =(BookHome)PortableRemoteObject.narrow(
homeObject,BookHome.class);
}
catch (Exception exception) {
exception.printStackTrace();
throw new ServletException("BookHome接口不能生成, 由于"
+ exception. getMessage());
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gb2312");
PrintWriter out = response.getWriter();
// 从用户端获取书名和图书购买量
String bookName = request.getParameter("bookname");
String inputQuantity = request.getParameter("quantity");
int bookQuantity = 0;
//确保用户输入的购买量的值为整数
try {
bookQuantity = Integer.parseInt(inputQuantity);
}
catch (NumberFormatException e) {
out.println("<P>" + inputQuantity +":不是整数<P>");
e.printStackTrace();
return;
}
try {
//产生一个书的实例
book = bookHome.create(bookName);
//调用容器管理的事务方法
book.bookSale_CMT(bookQuantity);
//调用Bean管理的事务方法
//book.bookSale_BMT(bookQuantity);
out.println("<H3>交易成功</H3>");
out.println("<H3>"+bookName+"-书库存:" +
book.getBookInStock()+ "</H3>");
out.println("<H3>销售总金额:" + book.getTotal()+ "</H3>");
out.close();
}
catch (InsufficientBookException e) {
e.printStackTrace();
out.println("<P>交易异常: " + e.getMessage() + "<P>");
out.println("<H3>"+bookName+"图书库存:" +
book.getBookInStock()+ "</H3>");
out.println("<H3>销售总金额:" + book.getTotal()+ "</H3>");
out.close();
}
catch(Exception e) {
e.printStackTrace();
out.println("异常:" + e.getMessage());
}
}
public void destroy() {
System.out.println("BookServlet: destroy()");
}
}
把上面代码文件保存在D:/j2ee_apps/chapter12目录中。
Ø 异常处理代码
u 文件名为InsufficientBookException.java
package book;
public class InsufficientBookException extends Exception {
public InsufficientBookException() {}
public InsufficientBookException(String msg) {
super(msg);
}
}
把上面代码文件保存在D:/j2ee_apps/chapter12目录中。
Ø 代码编译
D:/j2ee_apps/chapter12目录中应该有下述文件:
D:/j2ee_apps/chapter12/Book.java
D:/j2ee_apps/chapter12/BookHome.java
D:/j2ee_apps/chapter12/BookBean.java
D:/j2ee_apps/chapter12/BookServlet.java
D:/j2ee_apps/chapter12/InsufficientBookException.java
D:/j2ee_apps/chapter12/bookdeal.html
使用下列命令进行编译:
javac -d D:/j2ee_apps/chapter12 -classpath
C:/sun/appserver/lib/j2ee.jar D:/j2ee_apps/chapter12/*.java
这个命令较长,把它放在文本文件中。整个命令放在一行里,保存为以.bat结尾的批处理文件,然后在命令提示符下执行这个批处理文件。注意在chapter12目录中不要有其他的java源文件。
Ø 程序装配
在程序装配前应在J2EE环境下的PointBase数据库中创建例子中的两个数据表books和bookfund。本例中这两个表在jdbc:pointbase:test数据库中,如图12-5所示:
图12-5
程序的装配类似于第三章中会话Bean程序例子的装配过程。主要步骤有:
1. 创建应用程序ch12,Context Root设为ch12。
2. 添加名为bookdeal.html的页面文件。
3. 装配BookServlet组件,Aliases设为BookDeal。
4. 装配会话Bean组件,装配时选择有状态的会话Bean,如图12-6所示。组件的JNDI名为BookHome,可在general页的Sun-specific Setting中设置。
图12-6
5. 会话Bean组件的Resource Ref’s属性页中,各选项内容如下:
Coded Name: jdbc/BookDB (代码中使用的名字)
Type : javax.sql.DataSource,
Authentication: Container
Sharable: √
JNDI Name: jdbc/PointBase
User Name : pbpublic
Passward: pbpublic
其中jdbc/PointBase的设置应指向含有books和bookfund数据表的数据库。本例使用的是PointBase数据库,名为jdbc:pointbase:test。如果使用其他数据库,请参见上一章的JDBC内容完成相关设置。
如果在BookServlet中调用bookSale_CMT方法,那么应该选择容器管理的事务,会话Bean中相关方法的事务属性应设置为required。如果调用bookSale_BMT方法,则选择Bean管理的事务。可在本章开头的图12-1界面中选择事务管理的类型及方法的事务属性。
6. 添加异常处理类InsufficientBookException.class
安装后部署程序,运行前确保启动PointBase数据库。若部署时出现异常,有可能同上一章中SQL2000的JDBC驱动程序设置有关,应重新装配J2EE系统软件。
本章要求
1. 了解J2EE下事务的概念及类型
2. 了解事务属性的定义
3. 掌握容器管理事务的使用
4. 掌握会话Bean中变量的同步方法
5. 掌握Bean管理事务的使用
6. 熟悉本章例子中关于事务的程序代码