Mybatis原理之數據源和鏈接池

mybatis

在Java工程項目中,咱們常會用到Mybatis框架對數據庫中的數據進行增刪查改,其原理就是對 JDBC 作了一層封裝,並優化數據源的鏈接。java

​ 咱們先來回顧下 JDBC 操做數據庫的過程。mysql

JDBC 操做數據庫

JDBC 操做數據庫的時候須要指定 鏈接類型、加載驅動、創建鏈接、最終執行 SQL 語句,代碼以下:sql

public static final String url = "jdbc:mysql://127.0.0.1/somedb";  
    public static final String name = "com.mysql.jdbc.Driver";  
    public static final String user = "root";  
    public static final String password = "root";  
  
    public Connection conn = null;  
    public PreparedStatement pst = null;  
  
    public DBHelper(String sql) {  
        try {  
          	//指定鏈接類型
            Class.forName(name);
          	//創建鏈接 
            conn = DriverManager.getConnection(url, user, password); 
          	//準備執行語句  
            pst = conn.prepareStatement(sql);
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    public void close() {  
        try {  
            this.conn.close();  
            this.pst.close();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }

​ 一個SQL的執行,若是使用JDBC進行處理,須要通過 加載驅動、創建鏈接、再執行SQL的一個過程,當下一個SQL到來的時候,還須要進行一次這個流程,這就形成沒必要要的性能損失,並且隨着用戶操做的逐漸增多,每次都和數據庫創建鏈接對數據庫自己來講也是一種壓力。數據庫

​ 爲了減小這種沒必要要的消耗,能夠對數據的操做進行拆分。在Mybatis中,數據庫鏈接的創建和管理的部分叫作數據庫鏈接池。mybatis

Mybatis 數據源DateSource的分類

  • UNPOOLED 不使用鏈接池的數據源
  • POOLED 使用鏈接池的數據源
  • JNDI 使用JNDI實現的數據

Mybatis 數據源DateSource的分類

  • UNPOOLED

    UNPOOLED 不使用鏈接池的數據源,當 dateSource 的type屬性被配置成了UNPOOLEDMyBatis 首先會實例化一個UnpooledDataSourceFactory工廠實例,而後經過 .getDataSource() 方法返回一個UnpooledDataSource 實例對象引用,咱們假定爲dataSource框架

    ​ 使用 UnpooledDataSourcegetConnection() ,每調用一次就會產生一個新的 Connection 實例對象。UnPooledDataSourcegetConnection() 方法實現以下:性能

public class UnpooledDataSource implements DataSource {
    private ClassLoader driverClassLoader;
    private Properties driverProperties;
    private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
    private String driver;
    private String url;
    private String username;
    private String password;
    private Boolean autoCommit;
    private Integer defaultTransactionIsolationLevel;

    public UnpooledDataSource() {
    }

    public UnpooledDataSource(String driver, String url, String username, String password){
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;
    }
    
    public Connection getConnection() throws SQLException {
        return this.doGetConnection(this.username, this.password);
    }
    
    private Connection doGetConnection(String username, String password) throws SQLException {
        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();
        Connection connection = DriverManager.getConnection(this.url, properties);
        this.configureConnection(connection);
        return connection;
    }
}

如上代碼所示,UnpooledDataSource會作如下事情:優化

  1. 初始化驅動: 判斷driver驅動是否已經加載到內存中,若是尚未加載,則會動態地加載driver類,並實例化一個Driver對象,使用DriverManager.registerDriver()方法將其註冊到內存中,以供後續使用。this

  2. 建立Connection對象: 使用DriverManager.getConnection()方法建立鏈接。url

  3. 配置Connection對象: 設置是否自動提交autoCommit和隔離級別isolationLevel

  4. 返回Connection對象

從上述的代碼中能夠看到,咱們每調用一次getConnection()方法,都會經過DriverManager.getConnection()返回新的java.sql.Connection實例,因此沒有鏈接池。
  • ###POOLED 數據源 鏈接池

PooledDataSource: 將java.sql.Connection對象包裹成PooledConnection對象放到了PoolState類型的容器中維護。 MyBatis將鏈接池中的PooledConnection分爲兩種狀態: 空閒狀態(idle)和活動狀態(active),這兩種狀態的PooledConnection對象分別被存儲到PoolState容器內的**idleConnectionsactiveConnections**兩個List集合中:

idleConnections: 空閒(idle)狀態PooledConnection對象被放置到此集合中,表示當前閒置的沒有被使用的PooledConnection集合,調用PooledDataSourcegetConnection()方法時,會優先今後集合中取PooledConnection對象。當用完一個java.sql.Connection對象時,MyBatis會將其包裹成PooledConnection對象放到此集合中。

activeConnections: 活動(active)狀態的PooledConnection對象被放置到名爲activeConnectionsArrayList中,表示當前正在被使用的PooledConnection集合,調用PooledDataSourcegetConnection()方法時,會優先從idleConnections集合中取PooledConnection對象,若是沒有,則看此集合是否已滿,若是未滿,PooledDataSource會建立出一個PooledConnection,添加到此集合中,並返回

如今讓咱們看一下popConnection()方法到底作了什麼:

  1. 先看是否有空閒(idle)狀態下的PooledConnection對象,若是有,就直接返回一個可用的PooledConnection對象;不然進行第2步。

  2. 查看活動狀態的PooledConnectionactiveConnections是否已滿;若是沒有滿,則建立一個新的PooledConnection對象,而後放到activeConnections池中,而後返回此PooledConnection對象;不然進行第三步;

  3. 看最早進入activeConnections池中的PooledConnection對象是否已通過期:若是已通過期,從activeConnections池中移除此對象,而後建立一個新的PooledConnection對象,添加到activeConnections中,而後將此對象返回;不然進行第4步。

  4. 線程等待,循環2步

/*
 * 傳遞一個用戶名和密碼,從鏈接池中返回可用的PooledConnection
 */
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)
    {
        synchronized (state)
        {
            if (state.idleConnections.size() > 0)
            {
                // 鏈接池中有空閒鏈接,取出第一個
                conn = state.idleConnections.remove(0);
                if (log.isDebugEnabled())
                {
                    log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                }
            }
            else
            {
                // 鏈接池中沒有空閒鏈接,則取當前正在使用的鏈接數小於最大限定值,
                if (state.activeConnections.size() < poolMaximumActiveConnections)
                {
                    // 建立一個新的connection對象
                    conn = new PooledConnection(dataSource.getConnection(), this);
                    @SuppressWarnings("unused")
                    //used in logging, if enabled
                    Connection realConn = conn.getRealConnection();
                    if (log.isDebugEnabled())
                    {
                        log.debug("Created connection " + conn.getRealHashCode() + ".");
                    }
                }
                else
                {
                    // Cannot create new connection 當活動鏈接池已滿,不能建立時,取出活動鏈接池的第一個,即最早進入鏈接池的PooledConnection對象
                    // 計算它的校驗時間,若是校驗時間大於鏈接池規定的最大校驗時間,則認爲它已通過期了,利用這個PoolConnection內部的realConnection從新生成一個PooledConnection
                    //
                    PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                    if (longestCheckoutTime > poolMaximumCheckoutTime)
                    {
                        // Can claim overdue connection
                        state.claimedOverdueConnectionCount++;
                        state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        state.accumulatedCheckoutTime += longestCheckoutTime;
                        state.activeConnections.remove(oldestActiveConnection);
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit())
                        {
                            oldestActiveConnection.getRealConnection().rollback();
                        }
                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        oldestActiveConnection.invalidate();
                        if (log.isDebugEnabled())
                        {
                            log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                        }
                    }
                    else
                    {

                        //若是不能釋放,則必須等待有
                        // Must wait
                        try
                        {
                            if (!countedWait)
                            {
                                state.hadToWaitCount++;
                                countedWait = true;
                            }
                            if (log.isDebugEnabled())
                            {
                                log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                            }
                            long wt = System.currentTimeMillis();
                            state.wait(poolTimeToWait);
                            state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                        }
                        catch (InterruptedException e)
                        {
                            break;
                        }
                    }
                }
            }

            //若是獲取PooledConnection成功,則更新其信息

            if (conn != null)
            {
                if (conn.isValid())
                {
                    if (!conn.getRealConnection().getAutoCommit())
                    {
                        conn.getRealConnection().rollback();
                    }
                    conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                    conn.setCheckoutTimestamp(System.currentTimeMillis());
                    conn.setLastUsedTimestamp(System.currentTimeMillis());
                    state.activeConnections.add(conn);
                    state.requestCount++;
                    state.accumulatedRequestTime += System.currentTimeMillis() - t;
                }
                else
                {
                    if (log.isDebugEnabled())
                    {
                        log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                    }
                    state.badConnectionCount++;
                    localBadConnectionCount++;
                    conn = null;
                    if (localBadConnectionCount > (poolMaximumIdleConnections + 3))
                    {
                        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.");
    }

    return conn;
}

java.sql.Connection對象的回收

​ 當咱們的程序中使用完Connection對象時,若是不使用數據庫鏈接池,咱們通常會調用 connection.close() 方法,關閉connection鏈接,釋放資源

調用過close()方法的Connection對象所持有的資源會被所有釋放掉,Connection對象也就不能再使用。那麼,若是咱們使用了鏈接池,咱們在用完了Connection對象時,須要將它放在鏈接池中,該怎樣作呢?

​ 可能你們第一個在腦海裏閃現出來的想法就是:我在應該調用con.close()方法的時候,不調用close()方法,將其換成將Connection對象放到鏈接池容器中的代碼!

怎樣實現Connection對象調用了close()方法,而實際是將其添加到鏈接池中

​ 這是要使用代理模式,爲真正的Connection對象建立一個代理對象,代理對象全部的方法都是調用相應的真正Connection對象的方法實現。當代理對象執行close()方法時,要特殊處理,不調用真正Connection對象的close()方法,而是將Connection對象添加到鏈接池中。

MyBatisPooledDataSourcePoolState內部維護的對象是PooledConnection類型的對象,而PooledConnection則是對真正的數據庫鏈接java.sql.Connection實例對象的包裹器。

PooledConnection對象內持有一個真正的數據庫鏈接java.sql.Connection實例對象和一個java.sql.Connection的代理:

其源碼以下:

class PooledConnection implements InvocationHandler {
    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class[]{Connection.class};
    private int hashCode = 0;
    private PooledDataSource dataSource;
    private Connection realConnection;
    private Connection proxyConnection;
    private long checkoutTimestamp;
    private long createdTimestamp;
    private long lastUsedTimestamp;
    private int connectionTypeCode;
    private boolean valid;

    public PooledConnection(Connection connection, PooledDataSource dataSource) {
        this.hashCode = connection.hashCode();
        this.realConnection = connection;
        this.dataSource = dataSource;
        this.createdTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = System.currentTimeMillis();
        this.valid = true;
        this.proxyConnection = (Connection)Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
      	//當close時候,會回收 connection , 不會真正的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();
                }

                return method.invoke(this.realConnection, args);
            } catch (Throwable var6) {
                throw ExceptionUtil.unwrapThrowable(var6);
            }
        }
    }
}

掃碼關注公衆號:java之旅

相關文章
相關標籤/搜索