Posted on 2007-02-06 12:24
dennis 阅读(998)
评论(0) 编辑 收藏 所属分类:
模式与架构
这篇文章的想法来自于过去的两篇文章:《设计自己的MVC框架》《设计模式之事务处理》
链接:
http://www.javaresearch.org/article/59935.htm
http://www.javaresearch.org/article/59043.htm
代码下载同样在www.126.com的邮箱里,用户名 sharesources 密码 javafans
本文只是学习性质的文章,我一开始的想法就是修改《设计模式之事务处理》,提供Annotation来提供事务支持,支持到方法级别。通过引入一个 @Transaction标注,如果被此标注的方法将自动享受事务处理。目的是学习下Annotation和加深下对声明式事务处理的理解。
Annotation是JDK5引入的新特性,现在越来越多的框架采用此特性来代替烦琐的xml配置文件,比如hibernate,ejb3, spring等。对Annotation不了解,请阅读IBM网站上的文章,还有推荐javaeye的Annotation专栏:http: //www.javaeye.com/subject/Annotation
代码的示例是一个简单的用户管理例子。
首先,环境是mysql+jdk5+myeclipse5+tomcat5,在mysql中建立一张表adminusers:
create table adminusers(id int(10) auto_increment not null primary key,
name varchar(10) not null,
password varchar(10) not null,
user_type varchar(10));
然后在tomcat下建立一个数据源,把代码中的strutslet.xml拷贝到tomcat安装目录下的 /conf/Catalina/localhost目录里,请自行修改文件中的数据库用户名和密码,以及数据库名称。另外,把mysql的 jdbc驱动拷贝到tomcat安装目录下的common/lib目录。这样数据源就建好了。在web.xml中引用:
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/test</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
我的例子只是在《设计模式之事务处理》的基础上改造的,在那篇文章里,我讲解了自己对声明式事务处理的理解,并利用动态代理实现了一个 TransactionWrapper(事务包装器),通过业务代理工厂提供两种版本的业务对象:经过事务包装的和未经过事务包装的。我们在默认情况下包装业务对象中的所有方法,但实际情况是,业务对象中的很多方法不用跟数据库打交道,它们根本不需要包装在一个事务上下文中,这就引出了,我们为什么不提供一种方式来配置哪些方法需要事务控制而哪些并不需要?甚至提供事务隔离级别的声明?很自然的想法就是提供一个配置文件,类似spring式的事务声明。既然JDK5已经引入Annotation,相比于配置文件的烦琐和容易出错,我们定义一个@Transaction的annotation来提供此功能。
看下Transaction.java的代码:
package com.strutslet.db;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.Connection;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Transaction {
//事务隔离级别,默认为read_committed
public int level() default Connection.TRANSACTION_READ_COMMITTED ;
}
@Transaction 标注只有一个属性level,level表示事务的隔离级别,默认为Read_Committed(也是一般JDBC驱动的默认级别,JDBC驱动默认级别一般于数据库的隔离级别一致)。 @Target(ElementType.METHOD)表示此标注作用于方法级别, @Retention(RetentionPolicy.RUNTIME)表示在运行时,此标注的信息将被加载进JVM并可以通过Annotation的 API读取。我们在运行时读取Annotation的信息,根据隔离级别和被标注的方法名决定是否将业务对象的方法加进事务控制。我们只要稍微修改下 TransactionWrapper:
//TransactionWrapper.java
package com.strutslet.db;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import com.strutslet.exception.SystemException;
public 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) {
// Cache the wrapped delegate, so we can pass method invocations
// to it.
this.delegate = delegate;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
Connection con = ConnectionManager.getConnection();
//得到Transaction标注
Transaction transaction = method.getAnnotation(Transaction.class);
//如果不为空,说明代理对象调用的方法需要事务控制。
if (transaction != null) {
// System.out.println("transaction.." + con.toString());
// 得到事务隔离级别信息
int level = transaction.level();
try {
if (con.getAutoCommit())
con.setAutoCommit(false);
//设置事务隔离级别
con.setTransactionIsolation(level);
//调用原始对象的业务方法
result = method.invoke(delegate, args);
con.commit();
con.setAutoCommit(true);
} catch (SQLException se) {
// Rollback exception will be thrown by the invoke method
con.rollback();
con.setAutoCommit(true);
throw new SystemException(se);
} catch (Exception e) {
con.rollback();
con.setAutoCommit(true);
throw new SystemException(e);
}
} else {
result = method.invoke(delegate, args);
}
return result;
}
}
}
现在,看下我们的UserManager业务接口,请注意,我们是使用动态代理,只能代理接口,所以要把@Transaction标注是接口中的业务方法(与EJB3中的Remote,Local接口类似的道理):
package com.strutslet.demo.service;
import java.sql.SQLException;
import com.strutslet.db.Transaction;
import com.strutslet.demo.domain.AdminUser;
public interface UserManager {
//查询,不需要事务控制
public boolean checkUser(String name, String password) throws SQLException;
//新增一个用户,需要事务控制,默认级别
@Transaction
public boolean addUser(AdminUser user) throws SQLException;
}
要把addUser改成其他事务隔离级别(比如oracle的serializable级别),稍微修改下:@Transaction(level=Connection.TRANSACTION_SERIALIZABLE)
public boolean addUser(AdminUser user) throws SQLException;
不准备详细解释例子的业务流程,不过是登录和增加用户两个业务方法,看下就明白。阅读本文前最好已经读过开头提过的两篇文章。我相信代码是最好的解释:)