莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          使用Annotation設(shè)計(jì)持久層

          Posted on 2007-02-06 12:24 dennis 閱讀(1004) 評論(0)  編輯  收藏 所屬分類: 模式與架構(gòu)
          ?這篇文章的想法來自于過去的兩篇文章:《設(shè)計(jì)自己的MVC框架》《設(shè)計(jì)模式之事務(wù)處理》
          鏈接:
          http://www.javaresearch.org/article/59935.htm
          http://www.javaresearch.org/article/59043.htm


          代碼下載同樣在www.126.com的郵箱里,用戶名 sharesources 密碼 javafans

          ??? 本文只是學(xué)習(xí)性質(zhì)的文章,我一開始的想法就是修改《設(shè)計(jì)模式之事務(wù)處理》,提供Annotation來提供事務(wù)支持,支持到方法級別。通過引入一個(gè) @Transaction標(biāo)注,如果被此標(biāo)注的方法將自動享受事務(wù)處理。目的是學(xué)習(xí)下Annotation和加深下對聲明式事務(wù)處理的理解。

          ??? Annotation是JDK5引入的新特性,現(xiàn)在越來越多的框架采用此特性來代替煩瑣的xml配置文件,比如hibernate,ejb3, spring等。對Annotation不了解,請閱讀IBM網(wǎng)站上的文章,還有推薦javaeye的Annotation專欄:http: //www.javaeye.com/subject/Annotation

          ??? 代碼的示例是一個(gè)簡單的用戶管理例子。

          ??? 首先,環(huán)境是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下建立一個(gè)數(shù)據(jù)源,把代碼中的strutslet.xml拷貝到tomcat安裝目錄下的 /conf/Catalina/localhost目錄里,請自行修改文件中的數(shù)據(jù)庫用戶名和密碼,以及數(shù)據(jù)庫名稱。另外,把mysql的 jdbc驅(qū)動拷貝到tomcat安裝目錄下的common/lib目錄。這樣數(shù)據(jù)源就建好了。在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>
          ???
          ??? 我的例子只是在《設(shè)計(jì)模式之事務(wù)處理》的基礎(chǔ)上改造的,在那篇文章里,我講解了自己對聲明式事務(wù)處理的理解,并利用動態(tài)代理實(shí)現(xiàn)了一個(gè) TransactionWrapper(事務(wù)包裝器),通過業(yè)務(wù)代理工廠提供兩種版本的業(yè)務(wù)對象:經(jīng)過事務(wù)包裝的和未經(jīng)過事務(wù)包裝的。我們在默認(rèn)情況下包裝業(yè)務(wù)對象中的所有方法,但實(shí)際情況是,業(yè)務(wù)對象中的很多方法不用跟數(shù)據(jù)庫打交道,它們根本不需要包裝在一個(gè)事務(wù)上下文中,這就引出了,我們?yōu)槭裁床惶峁┮环N方式來配置哪些方法需要事務(wù)控制而哪些并不需要?甚至提供事務(wù)隔離級別的聲明?很自然的想法就是提供一個(gè)配置文件,類似spring式的事務(wù)聲明。既然JDK5已經(jīng)引入Annotation,相比于配置文件的煩瑣和容易出錯(cuò),我們定義一個(gè)@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 {
          ?? ? ? //事務(wù)隔離級別,默認(rèn)為read_committed
          ?????? public int level() default Connection.TRANSACTION_READ_COMMITTED??? ;
          ??? }

          @Transaction 標(biāo)注只有一個(gè)屬性level,level表示事務(wù)的隔離級別,默認(rèn)為Read_Committed(也是一般JDBC驅(qū)動的默認(rèn)級別,JDBC驅(qū)動默認(rèn)級別一般于數(shù)據(jù)庫的隔離級別一致)。 @Target(ElementType.METHOD)表示此標(biāo)注作用于方法級別, @Retention(RetentionPolicy.RUNTIME)表示在運(yùn)行時(shí),此標(biāo)注的信息將被加載進(jìn)JVM并可以通過Annotation的 API讀取。我們在運(yùn)行時(shí)讀取Annotation的信息,根據(jù)隔離級別和被標(biāo)注的方法名決定是否將業(yè)務(wù)對象的方法加進(jìn)事務(wù)控制。我們只要稍微修改下 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標(biāo)注
          ??? ??? ??? Transaction transaction = method.getAnnotation(Transaction.class);

          ??? ??? ??? //如果不為空,說明代理對象調(diào)用的方法需要事務(wù)控制。
          ??? ??? ??? if (transaction != null) {
          ??? ??? ??? ??? // System.out.println("transaction.." + con.toString());
          ??? ??? ??? ??? // 得到事務(wù)隔離級別信息
          ??? ??? ??? ??? int level = transaction.level();
          ??? ??? ??? ??? try {
          ??? ??? ??? ??? ??? if (con.getAutoCommit())
          ??? ??? ??? ??? ??? ??? con.setAutoCommit(false);
          ??????????????????? //設(shè)置事務(wù)隔離級別
          ??? ??? ??? ??? ??? con.setTransactionIsolation(level);
          ??????????????????? //調(diào)用原始對象的業(yè)務(wù)方法
          ??? ??? ??? ??? ??? 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;
          ??? ??? }
          ??? }
          }

          現(xiàn)在,看下我們的UserManager業(yè)務(wù)接口,請注意,我們是使用動態(tài)代理,只能代理接口,所以要把@Transaction標(biāo)注是接口中的業(yè)務(wù)方法(與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 {
          ??? //查詢,不需要事務(wù)控制
          ??? public boolean checkUser(String name, String password) throws SQLException;

          ??? //新增一個(gè)用戶,需要事務(wù)控制,默認(rèn)級別
          ??? @Transaction
          ??? public boolean addUser(AdminUser user) throws SQLException;

          }

          要把a(bǔ)ddUser改成其他事務(wù)隔離級別(比如oracle的serializable級別),稍微修改下:@Transaction(level=Connection.TRANSACTION_SERIALIZABLE)
          public boolean addUser(AdminUser user) throws SQLException;

          不準(zhǔn)備詳細(xì)解釋例子的業(yè)務(wù)流程,不過是登錄和增加用戶兩個(gè)業(yè)務(wù)方法,看下就明白。閱讀本文前最好已經(jīng)讀過開頭提過的兩篇文章。我相信代碼是最好的解釋:)
          主站蜘蛛池模板: 余庆县| 巩义市| 宁化县| 安义县| 固安县| 樟树市| 平舆县| 南平市| 驻马店市| 南阳市| 三明市| 嵊州市| 凌海市| 连山| 巴塘县| 北安市| 侯马市| 万州区| 南投市| 浠水县| 察隅县| 丰台区| 勃利县| 云梦县| 重庆市| 和政县| 大城县| 准格尔旗| 五莲县| 潼关县| 精河县| 玉树县| 拜城县| 察雅县| 甘洛县| 九江市| 吴桥县| 北京市| 陆川县| 横峰县| 广平县|