Mybatis原理之數據源和連接池

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

​ 我們先來回顧下 JDBC 操作數據庫的過程。

JDBC 操作數據庫

JDBC 操作數據庫的時候需要指定 連接類型、加載驅動、建立連接、最終執行 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 數據源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()方法將其註冊到內存中,以供後續使用。

  2. 創建Connection對象: 使用DriverManager.getConnection()方法創建連接。

  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之旅

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章