J2EE事物处理

来源:互联网 发布:centos 7 syslog 编辑:程序博客网 时间:2024/04/30 09:42
 

第一点:事务划分(transaction demarcation)的概念是特别重要的。J2EE规范为事务划分描述了两种模式:编程性事务(programmatic)和声明性事务(declarative)。下表是对这两种模式的拆分:

声明性事务划分

编程性事务划分

程序员使用EJB的布署描述符声明事务属性

程序员担负编写事务逻辑代码的责任。

运行时环境(EJB容器)使用这些属性来自动的管理事务。

应用程序通过一个API接口来控制事务。

 

第二点:JDBC事务划分比JTA事务划分简单,但是JTA提供了更好的灵活性:

(1)使用JDBC的事务划分:

public void setAutoCommit(Boolean)
public Boolean getAutoCommit()
public void commit()
public void rollback()

使用JDBC事务划分,你能够把多个SQL语句组合到一个单一事务中。JDBC事务的缺点之一就是事务范围被限定在一个单一的数据库连接中。一个JDBC事务不能够跨越多个数据库。

对ADO中的事务划分有两个主要的策略。一种方法是使用DAO承担事务划分的责任;另一种是延期性事务,它把事务划分到调用DAO对象的方法中。如果你选择前者,你将要在DAO类中嵌入事务代码。如果你选择后者,事务代码将被写在DAO类的外部。

实例1展示了一个带有两种数据操作的DAO:创建(create)和更新(update):

public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);

 

实例2展示了一个简单的事务,事务划分代码是在DAO类的外部。注意:在这个例子中的调用者把多个DOA操作组合到这个事务中。

tx.begin(); // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction

 

(2)使用JTA(java transaction API)的事务划分:

JTA概要介绍:Java事务API(JTA;Java Transaction API)和它的同胞Java事务服务(JTS;Java Transaction Service)为J2EE平台提供了分布式事务服务。一个分布式事务(distributed transaction)包括一个事务管理器(transaction manager)和一个或多个资源管理器(resource manager)。一个资源管理器(resource manager)是任意类型的持久化数据存储。事务管理器(transaction manager)承担着所有事务参与单元者的相互通讯的责任。JTA 具有的三个主要的接口分别是 UserTransaction 接口、TransactionManager 接口和 Transaction 接口。这些接口共享公共的事务操作,例如 commit() 和 rollback(), 但是也包含特殊的事务操作,例如 suspend(),resume() 和 enlist(),它们只出现在特定的接口上,以便在实现中允许一定程度的访问控制。例如,UserTransaction 能够执行事务划分和基本的事务操作,而 TransactionManager 能够执行上下文管理。要用JTA来划分一个事务,应用程序调用javax.transaction.UserTransaction接口中的方法,用UserTransaction.begin()、UserTransaction.commit()和 UserTransaction.rollback()

 

(3)使用JTA和JDBC:

开发人员经常使用JDBC来作为DAO类中的底层数据操作。如果计划使用JTA来划分事务,你将需要一个实现了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource接口JDBC的驱动。实现了这些接口的驱动将有能力参与到JTA事务中。一个XADataSource对象是一个XAConnection对象的工厂。XAConnections是参与到JTA事务中的连接。
  你需要使用应用程序服务器管理工具来建立XADataSource对象。对于特殊的指令请参考应用程序服务器文档和JDBC驱动文档。
  J2EE应用程序使用JNDI来查找数据源。一旦应用程序有了一个数据源对象的引用,这会调用javax.sql.DataSource.getConnection()来获得数据库的连接。
  XA连接区别于非XA连接。要记住的是XA连接是一个JTA事务中的参与者。这就意味着XA连接不支持JDBC的自动提交特性。也就是说应用程序不必 在XA连接上调用java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,用程序应该使用UserTransaction.begin()、UserTransaction.commit()和 UserTransaction.rollback().

 

第三点:实例说明

事务处理应该做在service层,也许你也正这样做,但是否知道为什么这样做?为什么不放在DAO层做事务处理。显而易见的原因是业务层 接口的每一个方法有时候都是一个业务用例(User Case),它需要调用不同的DAO对象来完成一个业务方法。比如简单地以网上书店购书最后的确定定单为例,业务方法首先是调用。.注意要保证同一个线程内取的是相同的连接即可(可用ThreadLocal实现)

例如:首先是业务接口,针对接口,而不是针对类编程:
public interface BookStoreManager{
           public boolean buyBook(String bookId,int quantity)throws SystemException;
           ....其他业务方法
     }


 接下来就是业务接口的实现类??业务对象:
public class BookStoreManagerImpl implements BookStoreManager{
          public boolean buyBook(String bookId)throws SystemException{
               Connection conn=ConnectionManager.getConnection();//获取数据库连接
               boolean b=false;
               try{
                   conn.setAutoCommit(false);   //取消自动提交
                   BookDAO bookDAO=DAOFactory.getBookDAO();
                   CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
                     //尝试从库存中取书
                   if(BookDAO.reduceInventory(conn,bookId,quantity)){
                        BigDecimal price=BookDAO.getPrice(bookId);   //取价格
                        //从客户帐户中扣除price*quantity的费用
                        b= CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
                        ....
                        其他业务方法,如通知管理员,生成定单等.
                         ...
                        conn.commit();    //提交事务
                        conn.setAutoCommit(true);
                   }
                }catch(SQLException e){
                   conn.rollback();    //出现异常,回滚事务
                   con.setAutoCommit(true);
                   e.printStackTrace();
                   throws new SystemException(e);  
                }
                return b;
          }
     }

然后是业务代表工厂: 
   public final class ManagerFactory {
       public static BookStoreManager getBookStoreManager() {
          return new BookStoreManagerImpl();
       }
    }
这样的设计非常适合于DAO中的简单活动,我们项目中的一个小系统也是采用这样的设计方案,但是它不适合于更大规模的应用。首先,你有没有闻到代码重复的 bad smell?每次都要设置AutoCommit为false,然后提交,出现异常回滚,包装异常抛到上层,写多了不烦才怪,那能不能消除呢?其次,业务代 表对象现在知道它内部事务管理的所有的细节,这与我们设计业务代表对象的初衷不符。对于业务代表对象来说,了解一个与事务有关的业务约束是相当恰当的,但 是让它负责来实现它们就不太恰当了。再次,你是否想过嵌套业务对象的场景?业务代表对象之间的互相调用,层层嵌套,此时你又如何处理呢?你要知道按我们现 在的方式,每个业务方法都处于各自独立的事务上下文当中(Transaction Context),互相调用形成了嵌套事务,此时你又该如何处理?也许办法就是重新写一遍,把不同的业务方法集中成一个巨无霸包装在一个事务上下文中。
我们有更为优雅的设计来解决这类问题,如果我们把Transaction Context的控制交给一个被业务代表对象、DAO和其他Component所共知的外部对象。当业务代表对象的某个方法需要事务管理时,它提示此外部 对象它希望开始一个事务,外部对象获取一个连接并且开始数据库事务。也就是将事务控制从service层抽离,当 web层调用service层的某个业务代表对象时,返回的是一个经过Transaction Context外部对象包装(或者说代理)的业务对象。此代理对象将请求发送给原始业务代表对象,但是对其中的业务方法进行事务控制。那么,我们如何实现 此效果呢?答案是JDK1.3引进的动态代理技术。动态代理技术只能代理接口,这也是为什么我们需要业务接口BookStoreManager的原因。
 首先,我们引入这个Transaction Context外部对象,它的代码其实很简单,如果不了解动态代理技术的请先阅读其他资料。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import com.strutslet.demo.service.SystemException;
public final class TransactionWrapper {
     /**
      * 装饰原始的业务代表对象,返回一个与业务代表对象有相同接口的代理对象
      */
     public static Object decorate(Object delegate) {
         return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
                 delegate.getClass().getInterfaces(), new XAWrapperHandler(
                         delegate));
     }
     //动态代理技术
     static final class XAWrapperHandler implements InvocationHandler {
         private final Object delegate;
         XAWrapperHandler(Object delegate) {
            this.delegate = delegate;
         }
        //简单起见,包装业务代表对象所有的业务方法
         public Object invoke(Object proxy, Method method, Object[] args)
                 throws Throwable {
             Object result = null;
             Connection con = ConnectionManager.getConnection();
             try {
                 //开始一个事务
                 con.setAutoCommit(false);
                 //调用原始业务对象的业务方法
                 result = method.invoke(delegate, args);
                 con.commit();    //提交事务
                 con.setAutoCommit(true);
             } catch (Throwable t) {
                 //回滚
                 con.rollback();
                 con.setAutoCommit(true);
                 throw new SystemException(t);
             }
             return result;
         }
     }
}

正如我们所见,此对象只不过把业务对象需要事务控制的业务方法中的事务控制部分抽取出来而已。请注意,业务代表对象内部调用自身的方法将不会开始新的事务,因为这些调用不会传给代理对象。如此,我们去除了代表重复的味道。此时,我们的业务代表对象修改成:
public class BookStoreManagerImpl implements BookStoreManager {
     public boolean buyBook(String bookId)throws SystemException{
           Connection conn=ConnectionManager.getConnection();// 获取数据库连接
           boolean b=false;
           try{
               BookDAO bookDAO=DAOFactory.getBookDAO();
               CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
               // 尝试从库存中取书
               if(BookDAO.reduceInventory(conn,bookId,quantity)){
                   BigDecimal price=BookDAO.getPrice(bookId);   // 取价格
                   // 从客户帐户中扣除price*quantity的费用
                   b= CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
                   ....
                   其他业务方法,如通知管理员,生成定单等.
                   ...
               }
           }catch(SQLException e){
              throws new SystemException(e);
           }
           return b;
     }
     ....
}
可以看到,此时的业务代表对象专注于实现业务逻辑,它不再关心事务控制细节,把它们全部委托给了外部对象。业务代表工厂也修改一下,让它返回两种类型的业务代表对象:
public final class ManagerFactory {
       //返回一个被包装的对象,有事务控制能力
       public static BookStoreManager getBookStoreManagerTrans() {
           return (BookStoreManager) TransactionWrapper
                   .decorate(new BookStoreManagerImpl());
       }
       //原始版本
       public static BookStoreManager getBookStoreManager() {
          return new BookStoreManagerImpl();
       }
       ......
    }
我们在业务代表工厂上提供了两种不同的对象生成方法:一个用于创建被包装的对象,它会为每次方法调用创建一个新的事务;另外一个用于创建未被包装的版本,它用于加入到已有的事务(比如其他业务代表对象的业务方法),解决了嵌套业务代表对象的问题。
我们的设计还不够优雅,比如我们默认所有的业务代表对象的方法调用都将被包装在一个Transaction Context。可事实是很多方法也许并不需要与数据库打交道,如果我们能配置哪些方法需要事务声明,哪些不需要事务管理就更完美了。解决办法也很简单, 一个XML配置文件来配置这些,调用时判断即可。说到这里,了解spring的大概都会意识到这不正是声明式事务控制吗?正是如此,事务控制就是AOP的 一种服务,spring的声明式事务管理是通过AOP实现的。AOP的实现方式包括:动态代理技术,字节码生成技术(如CGLIB库),java代码生成 (早期EJB采用),修改类装载器以及源代码级别的代码混合织入(aspectj)等。我们这里就是利用了动态代理技术,只能对接口代理;对类的动态代理 可以使用cglib库

 

第四点:总结

(1)在这DAO类使用事物总结如下:
事务划分代码被嵌入到DAO类内部
DAO类使用JDBC API来进行事务划分
调用者没有划分事务的方法
事务范围被限定在一个单一的JDBC连接
(2)JDBC事务对复杂的企业应用程序不总是有效的。如果你的事务将跨越多个DAO对象或多个数据库,那么下面的实现策略可能会更恰当:
用JTA对事务进行划分
事务划分代码被DAO分开
调用者承担划分事务的责任
DAO参与一个全局的事务中
(3)JDBC方法由于它的简易性而具有吸引力,JTA方法提供了更多灵活性。你选择什么样的实现将依赖于你的应用程序的特定需求。

 

原创粉丝点击