天天看點

使用Annotation設計持久層

這篇文章的想法來自于過去的兩篇文章:《設計自己的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();

                    throw new systemexception(se);

                } catch (exception e) {

                    throw new systemexception(e);

                }

            } else {

                result = method.invoke(delegate, args);

            }

            return result;

}

現在,看下我們的usermanager業務接口,請注意,我們是使用動态代理,隻能代理接口,是以要把@transaction标注是接口中的業務方法(與ejb3中的remote,local接口類似的道理):

package com.strutslet.demo.service;

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;

不準備詳細解釋例子的業務流程,不過是登入和增加使用者兩個業務方法,看下就明白。閱讀本文前最好已經讀過開頭提過的兩篇文章。我相信代碼是最好的解釋:)

文章轉自莊周夢蝶  ,原文釋出5.16