Java设计模式之事务处理

mng_boy

贡献于2011-07-13

字数:0 关键词: Java开发 Java

JAVAJAVAJAVA JAVA 设计模式之事务 处理 事务处理是企业应用需要解决的最主要的问题之一。 J2EE通过 JTA提供了完整的事务管理能力,包括多个事务性资 源的管理能力。 但 是大部分应用都是运行在单一的事务性资源之上 ( 一个数据库) , 他 们并不需要全局性的事务服 务。 本地事务服务已然足够 (比如 JDBC事务管理)。 本文并不讨论应该采用何种事务处理方式, 主 要目的是讨论如何更为优雅地设计事务服务。 仅 以 JDBC事务处理为例。 涉 及到的 DAO, Factory,Proxy,Decorator等模式概念,请阅读相关资料。 也许你听说过,事务处理应 该做在 service层,也许你也正这样做,但 是否知道为 什么这样做 ?为什么不 放在 DAO层做事务处理。 显而易见的原因是业务层接口的每一个方法有时候都是一个业务用例 ( User Case) , 它需 要 调 用 不 同 的 DAO对象来完成一个业务方 法。 比如简单地以网上书店购书最后的确定定单为例,业务方法首先是调用 BookDAO对象(一般是通过 DAO工厂产生),BookDAO判断 是否还有库存余量, 取得该书的价格信息等, 然 后调用 CustomerDAO从帐户扣除相应的费用以及记录信息, 然后是其他服务 ( 通知 管 理员等) 。 简化业务流程大概如此 : 注意 ,我们的例子忽略了连接的处理,只要保证同一个线程内取的是相同的连接即可(可用 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库。 这篇 短文只是介绍 下我对事务上下文模式以及声明式 事务管理实现基本原理的理解,如 有错误,请不吝赐教,谢谢。我的 email:killme2008@gmail.com

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档

相关文档