mybatis datasource的工廠方法模式(深度好文)

工廠方法模式是使用抽象工廠(抽象類或接口)來生產抽象產品(抽象類或接口)的一個過程,由抽象工廠來決定抽象產品的生產過程,實際生產中由具體的工廠子類或者實現類來完成具體的產品子類或者實現類的生產。具體例子請參考 設計模式整理 java

在mybatis的datasource中,它的抽象工廠爲sql

package org.apache.ibatis.datasource;

import java.util.Properties;
import javax.sql.DataSource;

public interface DataSourceFactory {
    //設置DataSource的相關屬性,通常緊跟在初始化完成以後
    void setProperties(Properties var1);
    //獲取DataSource對象
    DataSource getDataSource();
}

而它的抽象產品則爲jdk中的DataSource接口數據庫

package javax.sql;

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

public interface DataSource  extends CommonDataSource, Wrapper {

  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;
}

Mybatis中有兩個具體的產品——UnpooledDataSource和PooledDataSource,咱們先來看一下UnpooledDataSource,這是一個知足基本功能的沒有鏈接池的DataSource產品,跟以前的自實現的事務差很少,具體參考 Spring事務說明與自實現 。能夠先看到它的屬性以及靜態代碼塊,加載DriverManager中註冊的JDBC Driver複製一份到registeredDrivers中。另外它有不少的構造器。而抽象產品(DataSource接口)中的兩個getConnection方法都是由doGetConnection方法來實現的。apache

    //加載Driver類的類加載器
    private ClassLoader driverClassLoader;
    //數據庫鏈接驅動的相關配置
    private Properties driverProperties;
    //緩存全部已註冊的數據庫鏈接驅動
    private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
    //數據庫鏈接的驅動名稱
    private String driver;
    //數據庫URL
    private String url;
    //用戶名
    private String username;
    //密碼
    private String password;
    //是否自動提交
    private Boolean autoCommit;
    //事務隔離級別
    private Integer defaultTransactionIsolationLevel;
//將DriverManager中註冊的JDBC驅動複製一份到registeredDrivers中
static {
    Enumeration drivers = DriverManager.getDrivers();

    while(drivers.hasMoreElements()) {
        Driver driver = (Driver)drivers.nextElement();
        registeredDrivers.put(driver.getClass().getName(), driver);
    }

}
//接口中的方法
public Connection getConnection() throws SQLException {
    return this.doGetConnection(this.username, this.password);
}

public Connection getConnection(String username, String password) throws SQLException {
    return this.doGetConnection(username, password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
    //將各類屬性放入props中
    Properties props = new Properties();
    if(this.driverProperties != null) {
        props.putAll(this.driverProperties);
    }

    if(username != null) {
        props.setProperty("user", username);
    }

    if(password != null) {
        props.setProperty("password", password);
    }

    return this.doGetConnection(props);
}

private Connection doGetConnection(Properties properties) throws SQLException {
    //初始化驅動
    this.initializeDriver();
    //獲取數據庫鏈接(properties中包含了username以及password)
    Connection connection = DriverManager.getConnection(this.url, properties);
    //設置該鏈接的事務相關配置
    this.configureConnection(connection);
    return connection;
}

private synchronized void initializeDriver() throws SQLException {
    //若是已註冊的數據庫鏈接驅動沒有當前的驅動
    if(!registeredDrivers.containsKey(this.driver)) {
        try {
            Class driverType;
            //反射加載當前驅動,並初始化
            if(this.driverClassLoader != null) {
                driverType = Class.forName(this.driver, true, this.driverClassLoader);
            } else {
                driverType = Resources.classForName(this.driver);
            }
            //根據Class實例來獲取當前驅動自己的實例
            Driver driverInstance = (Driver)driverType.newInstance();
            //在DriverManager中註冊該驅動的代理
            DriverManager.registerDriver(new UnpooledDataSource.DriverProxy(driverInstance));
            //將該驅動添加到registeredDrivers中
            registeredDrivers.put(this.driver, driverInstance);
        } catch (Exception var3) {
            throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + var3);
        }
    }

}

private void configureConnection(Connection conn) throws SQLException {
    //設置是否自動提交事務
    if(this.autoCommit != null && this.autoCommit.booleanValue() != conn.getAutoCommit()) {
        conn.setAutoCommit(this.autoCommit.booleanValue());
    }
    //設置事務的隔離等級
    if(this.defaultTransactionIsolationLevel != null) {
        conn.setTransactionIsolation(this.defaultTransactionIsolationLevel.intValue());
    }

}

在DriverManager中是會去註冊Drivers的全部信息的設計模式

// 註冊JDBC驅動的列表
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
//註冊driver
public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da)
    throws SQLException {

    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        //若是列表中不存在該驅動,則添加至末尾,不然不添加
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }

    println("registerDriver: " + driver);

}

如今咱們來看一下與UnpooledDataSource對應的具體工廠實現類UnpooledDataSourceFactory。這種工廠,它只生產UnpooledDataSource這一種產品。緩存

首先咱們來看一下抽象工廠(DataSourceFactory接口)的setProperties方法安全

private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length();
protected DataSource dataSource = new UnpooledDataSource();
public void setProperties(Properties properties) {
    //Properties是繼承於HashTable的子類,不接受null值,線程安全,單線程下比HashMap慢,它的迭代器是枚舉型迭代器 enumerator 
    Properties driverProperties = new Properties();
    //MetaObject是mybatis自帶的一個反射工具,它的主要做用是經過反射給對象中擁有getter,setter的屬性,設置屬性值
    MetaObject metaDataSource = SystemMetaObject.forObject(this.dataSource);
    //獲取方法參數屬性集合的迭代器
    Iterator var4 = properties.keySet().iterator();
    //遍歷該屬性集合
    while(var4.hasNext()) {
        Object key = var4.next();
        String propertyName = (String)key;
        String value;
        //若是該屬性名稱以driver.開頭
        if(propertyName.startsWith("driver.")) {
            //獲取該屬性的值
            value = properties.getProperty(propertyName);
            //以"driver."開頭的配置項是對DataSource的配置,記錄到driverProperties中保存
            driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
        } else {  //若是不以driver.開頭
            //若是該dataSource中沒有Setter的屬性,則拋出異常
            if(!metaDataSource.hasSetter(propertyName)) {
                throw new DataSourceException("Unknown DataSource property: " + propertyName);
            }
            
            value = (String)properties.get(propertyName);
            //根據屬性類型進行類型轉換
            Object convertedValue = this.convertValue(metaDataSource, propertyName, value);
            //設置該dataSource的屬性
            metaDataSource.setValue(propertyName, convertedValue);
        }
    }
    //若是driverProperties有值
    if(driverProperties.size() > 0) {
        //設置dataSource的該屬性
        metaDataSource.setValue("driverProperties", driverProperties);
    }

}

抽象類工廠(DataSourceFactory接口)的getDataSource()的方法實現比較簡單網絡

public DataSource getDataSource() {
    return this.dataSource;
}

如今咱們來看一下它的另外一個產品PooledDataSource,這是一個帶數據庫鏈接池的產品。數據庫的鏈接過程很是耗時,數據庫可以創建的鏈接數也很是有限,因此在絕大多數系統中,數據庫鏈接是很是珍貴的資源,使用數據庫鏈接池就顯得尤其必要。數據庫鏈接池在初始化時,通常會建立必定數量的數據庫鏈接並添加到鏈接池中備用。當程序須要使用數據庫時,從池中請求鏈接;當程序再也不使用該鏈接時,會將其返回到池中緩存,等下下次使用,而不是直接關閉。而咱們以前寫的事務自實現中的鏈接則是每條線程執行完事務則會把鏈接關閉。mybatis

先看一下Mybatis中鏈接池類PooledConnection的實現,它是一個實現了動態代理的類app

class PooledConnection implements InvocationHandler

它的核心字段以下

//記錄當前PooledConnection對象所在的PooledDataSource對象。該PooledConnection是從該PooledDataSource中獲取的;當調用close()方法時會將PooledConnection放回
//該PooledDataSource中
private final PooledDataSource dataSource;
//真正的數據庫鏈接
private final Connection realConnection;
//數據庫鏈接的代理對象
private final Connection proxyConnection;
//從鏈接池中取出該鏈接的時間戳
private long checkoutTimestamp;
//該鏈接建立的時間戳
private long createdTimestamp;
//最後一次被使用的時間戳
private long lastUsedTimestamp;
//由數據庫URL,用戶名,密碼計算出來的hash值,可用於標識該鏈接所在的鏈接池
private int connectionTypeCode;
//檢測當前PooledConnection是否有效,主要是爲了防止程序經過close()方法將鏈接歸還給鏈接池以後,依然經過該鏈接操做數據庫
private boolean valid;

咱們來看一下它的動態代理方法invoke,它代理的其實是UnpooledDataSource中的getConnection方法返回的Connection自己實例的全部方法,用以對該鏈接進行加強。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    //若是調用的方法爲close,則將其從新放入鏈接池,而不是真正關閉數據庫鏈接
    if("close".hashCode() == methodName.hashCode() && "close".equals(methodName)) {
        this.dataSource.pushConnection(this);
        return null;
    } else {
        try {
            if(!Object.class.equals(method.getDeclaringClass())) {
                this.checkConnection(); //經過valid字段檢測鏈接是否有效
            }
            //調用真正數據庫鏈接對象的對應方法
            return method.invoke(this.realConnection, args);
        } catch (Throwable var6) {
            throw ExceptionUtil.unwrapThrowable(var6);
        }
    }
}
private void checkConnection() throws SQLException {
    if(!this.valid) {
        throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
    }
}

PoolState是用於管理PooledConnection對象狀態的組件,它經過兩個ArrayList<PooledConnection>集合分別管理空閒狀態的鏈接和活躍狀態的鏈接

//空閒的PooledConnection集合
protected final List<PooledConnection> idleConnections = new ArrayList();
//活躍的PooledConnection集合
protected final List<PooledConnection> activeConnections = new ArrayList();

其餘統計字段

//請求數據庫鏈接的次數
protected long requestCount = 0L;
//獲取鏈接的累計時間
protected long accumulatedRequestTime = 0L;
//記錄全部鏈接累計的checkout時長
protected long accumulatedCheckoutTime = 0L;
//當鏈接長時間未歸還給鏈接池時,會被認爲該鏈接超時,記錄超時的鏈接個數
protected long claimedOverdueConnectionCount = 0L;
//累計超時時間
protected long accumulatedCheckoutTimeOfOverdueConnections = 0L;
//累計等待時間
protected long accumulatedWaitTime = 0L;
//等待次數
protected long hadToWaitCount = 0L;
//無效的鏈接數
protected long badConnectionCount = 0L;

咱們再來看一下PooledDataSource這個產品,它的主要字段以下

//經過PoolState管理鏈接池的狀態並記錄統計信息
private final PoolState state = new PoolState(this);
//真正管理數據庫鏈接的對象,用於生成真實的數據庫鏈接對象
private final UnpooledDataSource dataSource;
//最大活躍鏈接數
protected int poolMaximumActiveConnections = 10;
//最大空閒鏈接數
protected int poolMaximumIdleConnections = 5;
//最大checkout時長
protected int poolMaximumCheckoutTime = 20000;
//在沒法獲取鏈接時,線程須要等待的時間
protected int poolTimeToWait = 20000;
// 每個嘗試從緩存池獲取鏈接的線程. 若是這個線程獲取到的是一個壞的鏈接,那麼這個數據源容許這個線程嘗試從新獲取一個新的鏈接,可是這個從新嘗試的次數不該該超 
//過 poolMaximumIdleConnections 與 poolMaximumLocalBadConnectionTolerance 之和。
protected int poolMaximumLocalBadConnectionTolerance = 3;
//在檢測一個數據庫是否可用時,會給數據庫發送一個測試SQL語句
protected String poolPingQuery = "NO PING QUERY SET";
//是否容許發送測試SQL語句
protected boolean poolPingEnabled;
//當鏈接超過poolPingConnectionsNotUsedFor毫秒未使用時,會發送一次測試SQL語句,檢測鏈接是否正常
protected int poolPingConnectionsNotUsedFor;
//根據數據庫的URL、用戶名和密碼生成的一個hash值,該哈希值用於標誌着當前的鏈接池
private int expectedConnectionTypeCode;

接口中要實現的兩個方法

public Connection getConnection() throws SQLException {
    return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
    return this.popConnection(username, password).getProxyConnection();
}

咱們能夠看到它都是由popConnection來實現的

private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;
    //若是鏈接代理爲空
    while(conn == null) {
        //獲取鏈接池狀態
        PoolState var8 = this.state;
        synchronized(this.state) {  //加鎖同步狀態
            //檢測空閒鏈接(不爲空)
            if(!this.state.idleConnections.isEmpty()) {
                //取出第一個鏈接(集合中第0個)
                conn = (PooledConnection)this.state.idleConnections.remove(0);
                if(log.isDebugEnabled()) {
                    log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                }
              //若是沒有空閒鏈接且活躍鏈接數沒有到達最大值
            } else if(this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
                //建立一個線程池鏈接,該鏈接其實是UnpooledDataSource的數據庫鏈接的代理對象實例
                conn = new PooledConnection(this.dataSource.getConnection(), this);
                if(log.isDebugEnabled()) {
                    log.debug("Created connection " + conn.getRealHashCode() + ".");
                }
            //若是沒有空閒鏈接且活躍鏈接數已經最大了,沒法建立新鏈接
            } else {
                //取得最先的活躍鏈接
                PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
                //獲取該鏈接的過時時間
                long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                //檢測該鏈接是否超時
                if(longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
                    //若是已超時,如下三條代碼都是對超時鏈接信息進行統計
                    ++this.state.claimedOverdueConnectionCount;
                    this.state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                    this.state.accumulatedCheckoutTime += longestCheckoutTime;
                    //將該超時的鏈接移除
                    this.state.activeConnections.remove(oldestActiveConnection);
                    //若是該超時鏈接的事務未提交
                    if(!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                        try {
                            //對該超時鏈接的事務進行回滾
                            oldestActiveConnection.getRealConnection().rollback();
                        } catch (SQLException var16) {
                            log.debug("Bad connection. Could not roll back");
                        }
                    }
                    //再次創建該超時鏈接的真實鏈接的新代理,由於該超時鏈接自己就是一個代理,須要拿到它的目標對象實例
                    conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                    conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                    conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                    //將該超時鏈接設置爲無效
                    oldestActiveConnection.invalidate();
                    if(log.isDebugEnabled()) {
                        log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                    }
                  //無空閒鏈接,活躍鏈接數最大沒法建立新鏈接,且無超時鏈接
                } else {
                    try {
                        if(!countedWait) {
                            //統計等待次數
                            ++this.state.hadToWaitCount;
                            countedWait = true;
                        }

                        if(log.isDebugEnabled()) {
                            log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
                        }
                        long wt = System.currentTimeMillis();
                        //線程阻塞等待
                        this.state.wait((long)this.poolTimeToWait);
                        //統計累計的等待時間
                        this.state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                    } catch (InterruptedException var17) {
                        break;
                    }
                }
            }
            //若是鏈接代理不爲空
            if(conn != null) {
                //檢測鏈接代理是否有效
                if(conn.isValid()) {
                    //若是有效且事務未提交則回滾事務
                    if(!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }
                    //配置鏈接代理的相關屬性
                    conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
                    conn.setCheckoutTimestamp(System.currentTimeMillis());
                    conn.setLastUsedTimestamp(System.currentTimeMillis());
                    //將該鏈接代理添加到活躍鏈接中
                    this.state.activeConnections.add(conn);
                    //進行相關統計
                    ++this.state.requestCount;
                    this.state.accumulatedRequestTime += System.currentTimeMillis() - t;
                //若是無效
                } else {
                    if(log.isDebugEnabled()) {
                        log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                    }
                    //進行一些錯誤鏈接的相關統計
                    ++this.state.badConnectionCount;
                    ++localBadConnectionCount;
                    conn = null;
                    if(localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {
                        if(log.isDebugEnabled()) {
                            log.debug("PooledDataSource: Could not get a good connection to the database.");
                        }

                        throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                    }
                }
            }
        }
    }

    if(conn == null) {
        if(log.isDebugEnabled()) {
            log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        }

        throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
    } else {
        return conn;
    }
}

由以上代碼可知,帶線程池的DataSource不過是對鏈接進行了一次動態代理,加強鏈接方法,並將代理鏈接放入集合中處理。

圖片來源網絡

如今咱們來看一下close()後將該鏈接代理從新放入鏈接池的相關代碼pushConnection(如下沒有特別說明,鏈接均指鏈接代理)

protected void pushConnection(PooledConnection conn) throws SQLException {
    //獲取鏈接池狀態
    PoolState var2 = this.state;
    synchronized(this.state) { //鎖同步該狀態
        //從活動鏈接中移除鏈接
        this.state.activeConnections.remove(conn);
        //判斷鏈接是否有效
        if(conn.isValid()) {
            //若是有效,檢測空閒鏈接池是否已達到上限以及該鏈接是不是鏈接池中的鏈接
            if(this.state.idleConnections.size() < this.poolMaximumIdleConnections && conn.getConnectionTypeCode() == this.expectedConnectionTypeCode) {
                //累計checkout時長
                this.state.accumulatedCheckoutTime += conn.getCheckoutTime();
                //回滾未提交的事務
                if(!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                //爲返還鏈接建立新的鏈接對象
                PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                //空閒鏈接加入該新對象
                this.state.idleConnections.add(newConn);
                newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
                //將原鏈接對象設置無效
                conn.invalidate();
                if(log.isDebugEnabled()) {
                    log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
                }
                //喚醒全部阻塞中的線程
                this.state.notifyAll();
              //空閒鏈接數已達到上限或該鏈接不屬於該鏈接池
            } else {
                //累計checkout時長
                this.state.accumulatedCheckoutTime += conn.getCheckoutTime();
                //回滾未提交的事務
                if(!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                //關閉真正的數據庫鏈接
                conn.getRealConnection().close();
                if(log.isDebugEnabled()) {
                    log.debug("Closed connection " + conn.getRealHashCode() + ".");
                }
                //將該鏈接設置爲無效
                conn.invalidate();
            }
        } else {
            if(log.isDebugEnabled()) {
                log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
            }
            //統計無效鏈接對象個數
            ++this.state.badConnectionCount;
        }

    }
}

這其中都有對鏈接池(鏈接代理)的有效監測conn.isValid(),咱們來看一下它的實現

public boolean isValid() {
    return this.valid && this.realConnection != null && this.dataSource.pingConnection(this);
}

其中this.dataSource.pingConnection(this)就是進行一個心跳檢測

protected boolean pingConnection(PooledConnection conn) {
    boolean result = true;

    try {
        //檢測真正的數據庫鏈接是否已經關閉
        result = !conn.getRealConnection().isClosed();
    } catch (SQLException var8) {
        if(log.isDebugEnabled()) {
            log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + var8.getMessage());
        }

        result = false;
    }
    //檢測是否運行測試SQL語句且長時間未使用的鏈接才須要ping操做來檢測數據庫鏈接是否正常
    if(result && this.poolPingEnabled && this.poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > (long)this.poolPingConnectionsNotUsedFor) {
        try {
            if(log.isDebugEnabled()) {
                log.debug("Testing connection " + conn.getRealHashCode() + " ...");
            }
            //執行心跳檢測SQL語句
            Connection realConn = conn.getRealConnection();
            Statement statement = realConn.createStatement();
            ResultSet rs = statement.executeQuery(this.poolPingQuery);
            rs.close();
            statement.close();
            if(!realConn.getAutoCommit()) {
                realConn.rollback();
            }

            result = true;
            if(log.isDebugEnabled()) {
                log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
            }
        } catch (Exception var7) {
            log.warn("Execution of ping query '" + this.poolPingQuery + "' failed: " + var7.getMessage());

            try {
                conn.getRealConnection().close();
            } catch (Exception var6) {
                ;
            }

            result = false;
            if(log.isDebugEnabled()) {
                log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + var7.getMessage());
            }
        }
    }

    return result;
}

如今咱們來看一下該dataSource對應的工廠PooledDataSourceFactory,它是UnpooledDataSourceFactory的子類,只是將UnpooledDataSourceFactory的dataSource屬性由UnpooledDataSource改爲了帶線程池的PooledDataSource。

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
    public PooledDataSourceFactory() {
        this.dataSource = new PooledDataSource();
}
}
相關文章
相關標籤/搜索