自定義框架之連接池和事務管理

一、事務的個人看法(僅供參考)

首先事務管理大家都知道是怎麼回事,不外呼就是對數據庫操作時是commit還是rollback,也就是提交當前操作的數據是正常提交還是回滾,如果回滾則不會將當前執行的sql語句永久性的保存到數據庫中。
當我們在程序中設計過程中通常會出現在提交數據到數據庫之前可能會出現業務上的問題,這個時候我們就沒有必要將錯誤的業務數據提交到數據庫,所以纔有了事務管理的概念
但是事務是在我們拿到數據庫連接對象的情況下才能夠進行操作的,傳統的情況就是在持久層拿到Connection對象後對數據的增刪改之前將自動提交事務先關閉,conn.setAutoCommit(false)
然後將方法體直接整個放在try-catch中,或者在具體的代碼塊中執行sql或者某個業務的代碼塊放在try-catch中,try中在最後調用executeUpdate()的時候提交,catch中則回滾,這個時候只是在數據庫操作出現錯誤纔回滾,可能是網絡原因或者是數據庫訪問比較大等等情況

在這兒說的有點囉嗦了,接下來介紹一下,自己做的一個事務管理的具體操作方式和具體代碼

二、出現的問題和解決方案

  • 開始使用java編寫事務的時候查詢了不少資料,比如spring的配置等等,可是在編碼期間卻出現一些問題。

  • 我的數據庫連接對象Connection是在持久層纔會拿到,但是我要不改變原有持久層所有代碼情況下,怎麼才能夠添加事務,這個問題很簡單,不外呼就是添加一個代理,將持久層的代碼重新封裝之後返回一個新的實例就行了

  • 前一個問題解決之後又產生了新的問題,雖然我們能夠通過代理拿到所有的方法,再在方法中添加事務管理,但是我怎麼才能拿到同一個數據庫連接對象呢,不可能我通過代理拿到一個別的數據庫連接對象然後添加事務管理吧,那肯定是錯誤的,兩個不同的數據連接對象之間並沒有任何聯繫,通過事務拿到的連接不可能操作用戶操作數據庫時候拿到的實際連接吧,所以在這就出現了卡頓了,不知道有沒有人和我遇到過相同的情況。這個時候我請求其他的項目經理還是老員工,但因爲比較忙所以就沒有得到很好的幫助,最後我只能依賴於度娘了,我堅持查找度娘多時,找到一個其他高手寫的事務管理,看了一下,操作比較簡單,只有幾個簡單的功能開啓事務,事務提交,事務回滾,關閉事務,這些都不是問題,只要看重點,人家是如何拿到同一個連接的,發現原來是通過數據連接池拿到的連接,然後數據連接池是用的c3p0,但是我這兒情況不一樣,數據庫連接池不是使用的第三方插件,而是自己編寫的一個數據庫連接池,而且裝連接的池子是用的棧stack來保存的,當用戶訪問數據庫的時候,就從棧中刪除掉這個連接,當數據庫訪問結束之後,就將這個連接再放回去,並沒有將數據庫連接關閉,這個時候我就需要思考了,我都將我的連接從數據庫連接池中給了用戶,不能再從連接池中去拿連接了,因爲拿到的連接都是新的連接,而不是用戶使用的那個連接,思考了一下,決定添加一個類來專門拿連接,不過不是通過DriverManager.getConnection(url,username,userpassword)來拿連接,而是通過數據庫連接池來獲取連接,這個時候用戶和事務管理類都是從一個地方拿的連接了,但是是不是通一個連接呢,這個問題肯定會想到,雖然都是從同一個類中拿的連接,但是添加的這個專門拿連接的類又不能做成單例模式,拿的連接其實都是通過實例化得到的,所以我帶着這個疑問,進行了驗證,我編寫的這個事務用到了多處使用代理,所以驗證就只需要在代理類中驗證就行了,最後得出結論,確實是通一個連接對象,怎麼看出來的呢,很簡單,直接打印他們的內存地址,也就是hashcode值就可以了,所以到這裏一個簡單的事務就製作成功了

三、根據代碼看具體情況

  • 首先編寫一個自定義的數據庫連接池,數據庫連接池對象創建爲一個單例模式,並且使用內部類的形式,進行懶加載,下邊有幾個類,再 一 一 介紹
  • 數據庫連接的配置文件jdbc.properties,配置信息中含有兩中數據庫的配置,第一個是mysql的配置信息,第二個是oracle的配置信息
#連接mysql數據庫的配置信息
#數據庫驅動程序
DB_DRIVER=com.mysql.jdbc.Driver
#數據庫訪問用戶
DB_NAME=root
#數據庫用戶的訪問密碼
DB_PASSWORD=120607
#數據庫訪問地址
DB_URL=jdbc:mysql://localhost:3307/employee?Unicode=true&characterEncoding=UTF-8
#連接池初始化數量
DB_INIT_CONNECTIONS=10
#連接池自動創建連接的數量
DB_INCREMENT_CONNECTIONS=10
#連接池最大連接數量
DB_MAX_CONNECTIONS=200
#數據庫類型
DB_TYPE=mysql

#連接oracle數據庫的配置信息
#數據庫驅動程序
#DB_DRIVER=oracle.jdbc.driver.OracleDriver
#數據庫訪問用戶
#DB_NAME=c##refng
#數據庫用戶的訪問密碼
#DB_PASSWORD=rf123
#數據庫訪問地址
#DB_URL=jdbc:oracle:thin:@127.0.0.1:1521:orcl
#連接池初始化數量
#DB_INIT_CONNECTIONS=10
#連接池自動創建連接的數量
#DB_INCREMENT_CONNECTIONS=10
#連接池最大連接數量
#DB_MAX_CONNECTIONS=200
#數據庫類型
#DB_TYPE=oracle
  • 數據庫連接配置類DB.java,主要是保存從配置文件中取出來的數據
package utils.pools;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

/**
 * 封裝數據庫連接需要的屬性
 * @author facebook
 *
 */
public final class DB {
    /**
     * 數據庫驅動
     */
    public static String DB_DRIVER;
    /**
     * 數據庫訪問名
     */
    public static String DB_NAME;
    /**
     * 數據庫訪問密碼
     */
    public static String DB_PASSWORD;
    /**
     * 數據庫訪問地址
     */
    public static String DB_URL;
    /**
     *初始化連接數
     */
    public static int DB_INIT_CONNECTIONS;
    /**
     * 自動擴容連接數
     */
    public static int DB_INCREMENT_CONNECTIONS;
    /**
     * 最大連接數
     */
    public static int DB_MAX_CONNECTIONS;
    /**
     * 數據庫種類
     */
    public static String DB_TYPE;
    /**
     * 類加載的時候只創建次數據庫訪問所需要的字符
     */
    static{
            Properties prop = new Properties();
            try {
                prop.load(new FileInputStream("D:\\TomcatWorkspace-Custom\\employee\\src\\utils\\jdbc\\jdbc.properties"));
                DB_DRIVER = prop.getProperty("DB_DRIVER");
                DB_NAME = prop.getProperty("DB_NAME");
                DB_PASSWORD = prop.getProperty("DB_PASSWORD");
                DB_URL = prop.getProperty("DB_URL");
                DB_INIT_CONNECTIONS = Integer.parseInt(prop.getProperty("DB_INIT_CONNECTIONS"));
                DB_INCREMENT_CONNECTIONS = Integer.parseInt(prop.getProperty("DB_INCREMENT_CONNECTIONS"));
                DB_MAX_CONNECTIONS = Integer.parseInt(prop.getProperty("DB_MAX_CONNECTIONS"));
                DB_TYPE = prop.getProperty("DB_TYPE").toUpperCase();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
    /**
     * 獲取數據庫驅動程序的類地址
     * com.mysql.jdbc.Driver
     * @return
     */
    public static String getDB_DRIVER() {
        return DB_DRIVER;
    }
    /**
     * 設置數據庫的驅動程序的類地址
     * @param dB_DRIVER
     */
    public static void setDB_DRIVER(String dB_DRIVER) {
        DB_DRIVER = dB_DRIVER;
    }
    /**
     * 獲取被訪問數據庫的名稱
     * @return
     */
    public static String getDB_NAME() {
        return DB_NAME;
    }
    /**
     * 設置被訪問數據庫的名稱
     * @param dB_NAME
     */
    public static void setDB_NAME(String dB_NAME) {
        DB_NAME = dB_NAME;
    }
    /**
     * 獲得數據庫訪問的密碼
     * @return
     */
    public static String getDB_PASSWORD() {
        return DB_PASSWORD;
    }
    /**
     * 設置數據庫訪問的密碼
     * @param dB_PASSWORD
     */
    public static void setDB_PASSWORD(String dB_PASSWORD) {
        DB_PASSWORD = dB_PASSWORD;
    }
    /**
     * 獲得數據庫訪問地址
     * @return
     */
    public static String getDB_URL() {
        return DB_URL;
    }
    /**
     * 設置數據庫訪問訪問地址
     * @param dB_URL
     */
    public static void setDB_URL(String dB_URL) {
        DB_URL = dB_URL;
    }
    /**
     * 獲取連接池初始化連接數
     * @return
     */
    public static int getDB_INIT_CONNECTIONS() {
        return DB_INIT_CONNECTIONS;
    }
    /**
     * 設置連接池初始化連接數
     * @param dB_INIT_CONNECTIONS
     */
    public static void setDB_INIT_CONNECTIONS(int dB_INIT_CONNECTIONS) {
        DB_INIT_CONNECTIONS = dB_INIT_CONNECTIONS;
    }
    /**
     * 獲得連接池自動擴容的數據量
     * @return
     */
    public static int getDB_INCREMENT_CONNECTIONS() {
        return DB_INCREMENT_CONNECTIONS;
    }
    /**
     * 設置連接池自動擴容的數量
     * @param dB_INCREMENT_CONNECTIONS
     */
    public static void setDB_INCREMENT_CONNECTIONS(int dB_INCREMENT_CONNECTIONS) {
        DB_INCREMENT_CONNECTIONS = dB_INCREMENT_CONNECTIONS;
    }
    /**
     * 獲取數據庫最大訪問量
     * @return
     */
    public static int getDB_MAX_CONNECTIONS() {
        return DB_MAX_CONNECTIONS;
    }
    /**
     * 設置數據庫最大訪問量
     * @param dB_MAX_CONNECTIONS
     */
    public static void setDB_MAX_CONNECTIONS(int dB_MAX_CONNECTIONS) {
        DB_MAX_CONNECTIONS = dB_MAX_CONNECTIONS;
    }
    /**
     * 獲取數據庫類型
     * @return
     */
    public static String getDB_TYPE() {
        return DB_TYPE;
    }
    /**
     * 設置數據庫類型
     * @param dB_TYPE
     */
    public static void setDB_TYPE(String dB_TYPE) {
        DB_TYPE = dB_TYPE;
    }

}
  • 通過DriverManager.getConnection(url,user,password)拿到鏈接的類,該類中的連接可設值,並且含有一個屬性,是否空閒,這時爲了防止同一個連接被多個用戶爭奪DBConn.java
package utils.pools;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBConn {
    /**
     * 數據庫連接狀態
     * true:表示該連接正在使用,false:表示該連接處於空閒狀態
     */
    private  boolean isFree = true;
    /**
     * 數據庫連接對象
     */
    private Connection conn;
    /**
     * 加載數據庫連接類的時候,只加載一次驅動程序,以後不再重複加載
     */
    static{
        try {
            Class.forName(DB.DB_DRIVER);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 設置當前的數據庫連接對象的使用狀態
     * 初始化true
     * @param isFree
     */
    public  void setIsFree(boolean isFree){
        this.isFree = isFree;
    }
    /**
     * 返回當前數據庫連接的使用狀態
     * @return
     */
    public boolean getIsFree(){
        return isFree;
    }
    /**
     * 獲取數據庫連接
     * @return
     */
    public Connection connection() {
        if(conn!=null){
            return conn;
        }
        try {
            conn =  DriverManager.getConnection(DB.DB_URL,DB.DB_NAME,DB.DB_PASSWORD);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    /**
     * 設置數據庫連接
     * @param conn
     */
    public void setConnection(Connection conn) {
        this.conn = conn;
    }
}
  • 數據庫連接池的具體實現類,創建連接使用了代理,將原有的連接中的close方法進行重寫之後返回一個被重新包裝過的連接對象,重寫close方法是爲了在調用Connection的close方法的時候不會將該連接關閉,而是放回到數據庫連接池中DBPool.java
package utils.pools;

import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Stack;


/**
 * 數據庫連接池,具體實現
 * 分析:
 *  數據庫連接池:
 *  存儲多個數據庫連接對象的集合
 * 
 *  數據庫連接對象:DBConn
 *  1.需要被操作的數據庫連接對象connection
 *     2.該鏈接是是否爲空閒狀態
 *     
 *  該連接池中的成員變量:
 *  1、數據連接初始連接數量
 *  2、數據連接最大連接數量
 *  3、數據庫連接自動擴容的數量
 *  4、存儲數據庫連接的集合stack,vector;
 * 
 *  連接池的創建和用戶使用數據庫連接:
 *  1、存儲數據連接的幾個對象,可使用堆棧或者序列或者向量來存儲,保證線程安全
 *  2、當用戶需要訪問數據庫的時候,需要從線程池中獲取一個數據庫連接的權利,
 *      但是需要判斷當前的數據庫中是否含有空閒的數據庫連接,
 *      如果含有那麼就將含有的數據庫連接開發給這個請求數據庫連接的用戶使用
 *      如果已經沒有足夠的連接數量提供給該用戶,也就當前連接數量已經大於數據連接池的最大允許連接數量
 *      那麼就讓該用戶等該一小會,在看連接池中是否含有空餘的連接,如果還是沒有,
 *      那麼就根據連接池可允許的自動擴容數量,增加一定數量的數據庫連接,讓其他用戶可以正常的訪問數據庫
 *      當用戶拿到數據庫連接的使用權之後,需要將改數據庫的連接狀態改爲工作中,避免多個用戶拿到相同的連接
 *  3、當用戶使用完數據連接之後,這個時候需要返還數據庫的連接,也就是將當前的數據庫連接再次放回連接池
 *      而且需要將數據庫連接對象的狀態更改爲空閒
 *  
 *     獲取連接使用動態代理來實現
 *     
 * @author facebook
 *
 */
public class DBPool {
    /**
     * 存儲數據庫連接的集合,即數據連接池
     */
    private  Stack<DBConn> dbPoolStack=null;
    /**
     *  統計當前連接數量,避免連接數多於連接的創建
     */
    private int current;
    /**
     * 實例化連接池對象
     * 通過靜態內部類來實現懶加載創建實例
     */
    private static class DBPoolnstance{
        private static final DBPool dbPool = new DBPool();
    }

    /**
     * 將數據庫連接池對象的構造函數私有化,防止其他對象對它實例化
     * 初始化一定數量的數據庫連接,創建出有一定數據量數據連接的數據連接池
     * 在數據庫連接池類對象被調用的時候就加載出現一定數量的數據庫連接
     */
    private DBPool(){
        if(dbPoolStack==null){
            dbPoolStack = new Stack<DBConn>();
        }
        for(int i=0;i<DB.DB_INIT_CONNECTIONS;i++){
            dbPoolStack.push(createDBConn());
        }
    }
    /**
     * 返回連接池對象的實例
     * @return
     */
    public static DBPool getInstance(){
        return DBPoolnstance.dbPool;
    }
    /**
     * 創建一個數據庫連接的對象
     * @return
     */
    public  DBConn createDBConn(){
        DBConn dbconn = new DBConn();
        if(dbPoolStack.size()<11){
            try {
                //獲取數據庫元數據,用於查看數據庫所允許的最大數據庫連接數
                DatabaseMetaData dbMetaData = dbconn.connection().getMetaData();
                //得到當前訪問的數據庫的最大所允許的連接數
                int maxDBConnect = dbMetaData.getMaxConnections();
                //判斷數據庫的最大訪問量是否有設置,
                //如果設置,判斷當前設置的最大連接是否超過數據庫所允許的範圍,
                //如果超過,那麼將最大的數據庫連接數改爲數據庫所允許的最大連接數
                if(maxDBConnect>0&&maxDBConnect>DB.DB_MAX_CONNECTIONS){
                    DB.setDB_MAX_CONNECTIONS(maxDBConnect);
                }
                dbconn = DBInvocationHandler.getInstance().bind(dbconn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return dbconn;
    }
    /**
     * 獲得一個數據庫連接對象
     * 判斷獲取連接是否爲空閒,如果爲空閒,那麼則將該鏈接返回,並且將連接狀態設置爲不是空閒
     * @return
     */
    public DBConn getConnection(){
        DBConn dbconn = null;
        if(dbPoolStack.empty()){
            System.out.println("***************************");
            //先休眠一百毫秒,最多可循環兩次
            int conut = 0;
            conut = wait(100,conut);
            //如果兩次請求獲取連接都沒有空餘的連接,那麼新創建一個連接
            if(conut>=2){
                //如果當前的連接數量小於最大連接數-自動連接數,
                //則讓其創建新的連接,
                //否則如果沒有操作當前最大連接,則讓其創建一個連接
                if(current<DB.DB_MAX_CONNECTIONS-DB.DB_INCREMENT_CONNECTIONS){
                    incrementConection();
                }else{
                    dbPoolStack.add(createDBConn());
                }
            }else{
                getConnection();
            }
        }
        for(Iterator<DBConn> it = dbPoolStack.iterator();it.hasNext(); ){
            DBConn conn = (DBConn)it.next();
            //判斷數據庫連接是否空閒,如果是空閒的那麼就將該連接返回給用戶使用
            if(conn.getIsFree()){
                dbconn = dbPoolStack.pop();
                //將獲取到的數據庫連接狀態更改爲非空閒
                dbconn.setIsFree(false);
                break;
            }else{
                //否則說明該鏈接出現異常,將其刪除,並更新爲新的連接,保持連接池連接數量
                dbPoolStack.pop();
                dbPoolStack.push(new DBConn());
            }
        }
        current++;
        return dbconn;
    }
    /**
     * 釋放數據庫連接
     * 將當前使用結束的鏈接狀態設置爲空閒,並且將連接添加進連接池的棧中
     */
    public void releaseConnection(DBConn dbconn){
        try {
            dbconn.connection().close();
            dbPoolStack.push(dbconn);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 當沒有空餘連接的時候添加新的連接
     */
    public void incrementConection(){
        for(int i=0;i<DB.DB_INCREMENT_CONNECTIONS;i++){
            dbPoolStack.add(createDBConn());
        }
    }

    /**
     * 如果在獲取連接池中的連接時,發現連接池爲空,那麼請等待一段時間,時間過後再獲取連接,
     * 看有沒有返還鏈接的用戶,如果有則將該連接交給當前請求獲取連接的用戶,
     * 如果沒有,那麼新增一定數量的連接,以保證用戶訪問的流程
     * @param mSeconds
     */
    private int  wait(int ms,int conut) {
        try {
            // 嘗試讓本線程休眠一段時間,休眠的時長由參數mSeconds確定
            Thread.sleep(ms);
            conut++;
        } catch (InterruptedException e) {
            // 出現異常則輸出異常信息
            e.printStackTrace();
        }
        return conut;
    }
    /**
     * 獲取數據庫連接池的棧集合
     * @return
     */
    public Stack<DBConn> getDbPoolStack() {
        return dbPoolStack;
    }
}
  • 代理Connection的具體實現類,該類實現了InvocationHandler接口該接口中會創建一個返回代理對象的bind方法,誰需要通過代理重新封裝,就返回誰的實例對象,並且將會對接口中的所有方法進行過濾或者重新封裝DBInvocationHandler.java
package utils.pools;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

/**
 * 通過動態代理攔截Connection 中的close方法,
 * 實現數據連接池中的沒有一個數據庫連接都不會再調用 Connection中的close方法的時候出現數據庫連接頻繁關閉的情況
 * @author facebook
 *
 */
public class DBInvocationHandler implements InvocationHandler{
    /**
     * 被代理的類
     */
    private DBConn dbconn;
    private Connection conn;
    /**
     * 類加載則創建本類的實例
     */
    private static DBInvocationHandler dbInvocation = new DBInvocationHandler();
    /**
     * 返回本類的實例
     * @return
     */
    public static  DBInvocationHandler getInstance(){
        return dbInvocation;
    }
    /**
     * 將構造函數私有化
     */
    private DBInvocationHandler(){}
    /**
     * 攔截數據庫連接關閉動作
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //如果數據庫調用關閉連接操作,那麼將關閉連接的方法進行重寫
        Object obj = null;
        System.out.println("Connection正在調用:------------------"+method.getName()+"------------------方法");
        if("close".equals(method.getName())){
            //將數據庫連接對象的狀態設置爲空閒
            dbconn.setIsFree(true);
        }else{
            obj = method.invoke(conn, args);
        }
        return obj;
    }
    /**
     * 返回經過動態代理處理之後的數據庫訪問對象,
     * 主要攔截Connection中的close方法,重寫關閉數據庫操作,將被攔截的數據庫連接對象的操作設置爲空閒,
     * 其他不是close方法的繼續執行原有的功能
     * @return
     */
    public DBConn bind(DBConn dbconn){
        this.dbconn = dbconn;
        conn = dbconn.connection();
        Class<?> clz = dbconn.connection().getClass();
        Connection conn =  (Connection)Proxy.newProxyInstance(clz.getClassLoader(),new Class[]{Connection.class},this);
        dbconn.setConnection(conn);
        return dbconn;
    }
}
  • 提供給用戶獲取連接的類DBUtils.java
package utils.pools;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DBUtils{
    public Connection conn ;
    /**
     * 獲取連接
     * @return
     */
    public DBUtils(){
        DBPool dbPool = DBPool.getInstance();
        conn = dbPool.getConnection().connection();
    }
    /**
     * 創建Statement對象
     * @return
     * @throws SQLException
     */
    public  Statement statement() throws SQLException{
        return conn.createStatement();
    }
    /**
     * 創建PreparedStatement對象
     * @param sql
     * @return
     * @throws SQLException
     */
    public  PreparedStatement preparedStatement(String sql) throws SQLException{
        return conn.prepareStatement(sql);
    }
    /**
     * 創建存儲過程對象
     * {call insert_emp(emp_code in varchar2,rs out SYS_REFCURSOR)}
     * @param sql
     * @return
     * @throws SQLException
     */
    public  CallableStatement callableStatement(StringBuffer sql) throws SQLException{
        sql.insert(0, "{call ").append(" }");
        return conn.prepareCall(sql.toString());
    }
    /**
     * 創建ResultSet對象
     * @param sql
     * @return
     * @throws SQLException
     */
    public  ResultSet resultSet(String sql) throws SQLException{
        return preparedStatement(sql).executeQuery();
    }

    /**
     * 關閉使用對象
     * @param obj
     * @throws SQLException
     */
    public  void close(Object obj) throws SQLException{
        if(obj==null){
            return;
        }
        if(obj instanceof ResultSet){
            ((ResultSet) obj).close();
        }
        if(obj instanceof PreparedStatement){
            ((PreparedStatement) obj).close();
        }
        if(obj instanceof CallableStatement){
            ((CallableStatement) obj).close();
        }
    }
}
  • 事務管理類TransactionManager.java
package utils.transaction;

import java.sql.Connection;
import java.sql.SQLException;

import exception.BaseException;
import utils.pools.DBUtils;

/**
 * 事務管理類,主要是處理在對數據庫操作的時候添加事務提交,回滾等操作
 * @author facebook
 *
 */
public class TransactionManager {
    /**
     * 創建一個本地線程的局部變量的實例
     * 有點抽象,暫時不是很明白
     */
    public static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
    /**
     * 獲取數據庫連接類實例
     */
    private static DBUtils dbUtils = new DBUtils();
    /**
     * 開啓事務
     */
    public static void transaction(){
        try {
            Connection conn = dbUtils.conn;
            if(conn==null){
                throw new BaseException("沒有拿到連接!");
            }
            conn.setAutoCommit(false);
            local.set(conn);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 事務提交
     */
    public static void commit() {
        Connection conn = local.get();
        if (conn != null) {
            try {
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            // 清空threadLocal
            local.remove();
        }
    }

    /**
     *  事務回滾
     */
    public static void rollback() {
        Connection conn = local.get();
        if (conn != null) {
            try {
                conn.rollback();
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            // 清空threadLocal
            local.remove();
        }
    }
        /**
         * 關閉事務,並且調用數據庫關閉的動作
         * @throws SQLException
         */
        public static void close() throws SQLException {
            Connection conn = local.get();
            if (conn != null) {
                conn.close();
                // 清空threadLocal
                local.remove();
            }
        }
}
  • 下邊是具體的管理的代理類,該類含有兩個功能
  • 自動裝配接口對象的實例,類似於spring的Resource/Autowired/Conponent自動注入bean的註解
  • 對含有Transaction註解的進行方法的事務管理
    因爲是自動實例化另外service和dao的接口都繼承同一個父接口,所以在父接口中添加了Transaction註解後,service和dao的接口中就都含有了,因此在代碼中進行了過濾,這兒只代理dao層中的數據庫操作方法 AutoNewInstance.java
package utils.automatic;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import annotation.Automatic;
import annotation.Transaction;
import exception.BaseException;
import utils.transaction.TransactionManager;

/**
 * 自動創建實例接口的實例對象
 * 
 * 對於繼承該類的所有子類中查看其接口屬性對象,
 * 如果對象中含有自動裝配的註解,那麼將對該屬性接口對象進行自動實例化
 * 
 * 注意:
 * 因爲需要動態給多個接口屬性創建代理,
 * 因此,必須將代理派生類創建爲成員變量,
 * 因爲,動態代理使用匿名類部類的方法來做實現,
 * 如果只爲一個創建,那麼可將被代理的派生類以局部變量最終形態傳入內部即可
 * 即:final T newInstance
 * @author facebook
 *
 * @param <T>
 */
public class AutoNewInstance<T> {
    /**
     * 被代理對象的派生類
     */
    private T newInstance;
    /**
     * 通過構造函數,自動爲接口創建實例,
     * 對該接口進行動態代理,實現事務的管理
     * 
     * 難點:事務得到的數據連接必須與正常操作所得到的連接時一致的
     * 解決:通過數據庫連接池拿到相應的連接
     */
    public AutoNewInstance() {
        newInstance();
    }
    /**
     * 動態代理與自動裝配的具體實現
     */
    @SuppressWarnings("unchecked")
    public void newInstance(){
        for (Field field : this.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Automatic.class)) {
                String implPath = field.getAnnotation(Automatic.class).value();
                if("".equals(implPath.trim())){
                    throw new BaseException("註解上需要配置實例的對象路徑!");
                }
                try {
                    Class<?> clz = Class.forName(implPath);
                    newInstance = (T) clz.newInstance();
//                  T obj = (T) new ProxyInvocationHandler().bind(t);
                    T t = newInstance;
//                  回滾dao層
                    if(implPath.contains("dao")){
                        t = (T)Proxy.newProxyInstance(field.getType().getClassLoader(), clz.getInterfaces(), new InvocationHandler(){
                                @Override
                                public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
                                    Object ob = null;
                                    if(method.isAnnotationPresent(Transaction.class)){
                                        TransactionManager.transaction();
                                        try {
                                            ob = method.invoke(newInstance, args);
                                            TransactionManager.commit();
                                        } catch (Exception e) {
                                            TransactionManager.rollback();
                                            e.printStackTrace();
                                        }finally{
                                            TransactionManager.close();
                                        }
                                    }else{
                                        ob = method.invoke(newInstance, args);
                                    }
                                    return ob;
                                }
                            }
                        );
                    }
                    field.setAccessible(true);
                    field.set(this, t);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 事務註解類,定義哪些方法需要添加事務管理Transaction.java
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 在方法上添加事務的註解
 * @author facebook
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Transaction {
    String value() default "";
}
  • 自動裝配註解,用於提醒類的自動實例化Automatic.java
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 註解適用於類的自動裝配
 * @author facebook
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Automatic {
    String value();
}
  • 框架類的代碼第一次編寫,現在處於學習階段,考慮不到位,很希望各位前輩批評指正,謝謝
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章