副标题#e#
事务处理惩罚是企业应用需要办理的最主要的问题之一。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();
}
}
#p#副标题#e#
这样的设计很是适合于DAO中的简朴勾当,我们项目中的一个小系统也是回收这样的设计方案,可是它不适合于更大局限的应用。首先,你有没有闻到代码反复的 bad smell?每次都要配置AutoCommit为false,然后提交,呈现异常回滚,包装异常抛到上层,写多了不烦才怪,那能不能消除呢?其次,业务代表工具此刻知道它内部事务打点的所有的细节,这与我们设计业务代表工具的初志不符。对付业务代表工具来说,相识一个与事务有关的业务约束是相当得当的,可是让它认真来实现它们就不太得当了。再次,你是否想过嵌套业务工具的场景?业务代表工具之间的相互挪用,层层嵌套,此时你又如那里理惩罚呢?你要知道按我们此刻的方法,每个业务要领都处于各自独立的事务上下文傍边(Transaction Context),相互挪用形成了嵌套事务,此时你又该如那里理惩罚?也许步伐就是从头写一遍,把差异的业务要了解合成一个巨无霸包装在一个事务上下文中。
#p#分页标题#e#
我们有更为优雅的设计来办理这类问题,假如我们把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;
}
....
}
可以看到,此时的业务代表工具专注于实现业务逻辑,它不再体贴事务节制细节,把它们全部委托给了外部工具。业务代表工场也修改一下,让它返回两种范例的业务代表工具:
#p#分页标题#e#
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库。
这篇随笔只是先容下我对事务上下文模式以及声明式事务打点实现根基道理的领略,如有错误,请不惜见教,感谢。