这篇文章的想法来自于过去的两篇文章《设计自己的MVC框架》《设计模式之事务处理》 链接 代码下载同样在的邮箱里用户名sharesources密码javafans 本文只是学习性质的文章我一开始的想法就是修改《设计模式之事务处理》提供Annotation来提供事务支持支持到方法级别通过引入一个@Transaction标注如果被此标注的方法将自动享受事务处理目的是学习下Annotation和加深下对声明式事务处理的理解 Annotation是JDK引入的新特性现在越来越多的框架采用此特性来代替烦琐的xml配置文件比如hibernateejbspring等对Annotation不了解请阅读IBM网站上的文章还有推荐javaeye的Annotation专栏///subject/Annotation 代码的示例是一个简单的用户管理例子 首先环境是mysql+jdk+myeclipse+tomcat在mysql中建立一张表adminusers: createtableadminusers(idint()auto_incrementnotnullprimarykey namevarchar()notnull passwordvarchar()notnull user_typevarchar());
然后在tomcat下建立一个数据源把代码中的strutsletxml拷贝到tomcat安装目录下的/conf/Catalina/localhost目录里请自行修改文件中的数据库用户名和密码以及数据库名称另外把mysql的jdbc驱动拷贝到tomcat安装目录下的common/lib目录这样数据源就建好了在webxml中引用 <resourceref> <description>DBConnection</description> <resrefname>jdbctest</resrefname> <restype>javaxsqlDataSource</restype> <resauth>Container</resauth> </resourceref>
我的例子只是在《设计模式之事务处理》的基础上改造的在那篇文章里我讲解了自己对声明式事务处理的理解并利用动态代理实现了一个TransactionWrapper(事务包装器)通过业务代理工厂提供两种版本的业务对象经过事务包装的和未经过事务包装的我们在默认情况下包装业务对象中的所有方法但实际情况是业务对象中的很多方法不用跟数据库打交道它们根本不需要包装在一个事务上下文中这就引出了我们为什么不提供一种方式来配置哪些方法需要事务控制而哪些并不需要?甚至提供事务隔离级别的声明?很自然的想法就是提供一个配置文件类似spring式的事务声明既然JDK已经引入Annotation相比于配置文件的烦琐和容易出错我们定义一个@Transaction的annotation来提供此功能 看下Transactionjava的代码 packagecomstrutsletdb; importjavalangannotationDocumented; importjavalangannotationElementType; importjavalangannotationRetention; importjavalangannotationRetentionPolicy; importjavalangannotationTarget; importjavasqlConnection; @Target(ElementTypeMETHOD) @Retention(RetentionPolicyRUNTIME) @Documented public@interfaceTransaction{ //事务隔离级别默认为read_committed publicintlevel()defaultConnectionTRANSACTION_READ_COMMITTED; }
@Transaction标注只有一个属性levellevel表示事务的隔离级别默认为Read_Committed(也是一般JDBC驱动的默认级别JDBC驱动默认级别一般于数据库的隔离级别一致)@Target(ElementTypeMETHOD)表示此标注作用于方法级别@Retention(RetentionPolicyRUNTIME)表示在运行时此标注的信息将被加载进JVM并可以通过Annotation的API读取我们在运行时读取Annotation的信息根据隔离级别和被标注的方法名决定是否将业务对象的方法加进事务控制我们只要稍微修改下TransactionWrapper: //TransactionWrapperjava packagecomstrutsletdb; importjavalangannotationAnnotation; importjavalangreflectInvocationHandler; importjavalangreflectMethod; importjavalangreflectProxy; importjavasqlConnection; importjavasqlSQLException; importcomstrutsletexceptionSystemException; publicclassTransactionWrapper{ publicstaticObjectdecorate(Objectdelegate){ returnProxynewProxyInstance(delegategetClass()getClassLoader() delegategetClass()getInterfaces()newXAWrapperHandler( delegate)); } staticfinalclassXAWrapperHandlerimplementsInvocationHandler{ privatefinalObjectdelegate; XAWrapperHandler(Objectdelegate){ //Cachethewrappeddelegatesowecanpassmethodinvocations //toit thisdelegate=delegate; } publicObjectinvoke(ObjectproxyMethodmethodObject[]args) throwsThrowable{ Objectresult=null; Connectioncon=ConnectionManagergetConnection(); //得到Transaction标注 Transactiontransaction=methodgetAnnotation(Transactionclass); //如果不为空说明代理对象调用的方法需要事务控制 if(transaction!=null){ //Systemoutprintln(transaction+contoString()); //得到事务隔离级别信息 intlevel=transactionlevel(); try{ if(congetAutoCommit()) consetAutoCommit(false); //设置事务隔离级别 consetTransactionIsolation(level); //调用原始对象的业务方法 result=methodinvoke(delegateargs); mit(); consetAutoCommit(true); }catch(SQLExceptionse){ //Rollbackexceptionwillbethrownbytheinvokemethod conrollback(); consetAutoCommit(true); thrownewSystemException(se); }catch(Exceptione){ conrollback(); consetAutoCommit(true); thrownewSystemException(e); } }else{ result=methodinvoke(delegateargs); } returnresult; } } }
现在看下我们的UserManager业务接口请注意我们是使用动态代理只能代理接口所以要把@Transaction标注是接口中的业务方法(与EJB中的RemoteLocal接口类似的道理): packagecomstrutsletdemoservice; importjavasqlSQLException; importcomstrutsletdbTransaction; importcomstrutsletdemodomainAdminUser; publicinterfaceUserManager{ //查询不需要事务控制 publicbooleancheckUser(StringnameStringpassword)throwsSQLException; //新增一个用户需要事务控制默认级别 @Transaction publicbooleanaddUser(AdminUseruser)throwsSQLException; }
要把addUser改成其他事务隔离级别(比如oracle的serializable级别)稍微修改下@Transaction(level=ConnectionTRANSACTION_SERIALIZABLE) publicbooleanaddUser(AdminUseruser)throwsSQLException; 不准备详细解释例子的业务流程不过是登录和增加用户两个业务方法看下就明白阅读本文前最好已经读过开头提过的两篇文章我相信代码是最好的解释) |