Mybatis原理——數(shù)據(jù)源和連接池

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

? 我們先來回顧下 JDBC 操作數(shù)據(jù)庫的過程。

JDBC 操作數(shù)據(jù)庫

JDBC 操作數(shù)據(jù)庫的時候需要指定 連接類型、加載驅(qū)動、建立連接、最終執(zhí)行 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); 
            //準備執(zhí)行語句  
            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的執(zhí)行,如果使用JDBC進行處理,需要經(jīng)過 加載驅(qū)動、建立連接、再執(zhí)行SQL的一個過程,當下一個SQL到來的時候,還需要進行一次這個流程,這就造成不必要的性能損失,而且隨著用戶操作的逐漸增多,每次都和數(shù)據(jù)庫建立連接對數(shù)據(jù)庫本身來說也是一種壓力。

? 為了減少這種不必要的消耗,可以對數(shù)據(jù)的操作進行拆分。在Mybatis中,數(shù)據(jù)庫連接的建立和管理的部分叫做數(shù)據(jù)庫連接池。

Mybatis 數(shù)據(jù)源DateSource的分類

  • UNPOOLED 不使用連接池的數(shù)據(jù)源
  • POOLED 使用連接池的數(shù)據(jù)源
  • JNDI 使用JNDI實現(xiàn)的數(shù)據(jù)
Mybatis 數(shù)據(jù)源DateSource的分類
  • UNPOOLED

    ? UNPOOLED 不使用連接池的數(shù)據(jù)源,當 dateSource 的type屬性被配置成了UNPOOLEDMyBatis 首先會實例化一個UnpooledDataSourceFactory工廠實例,然后通過.getDataSource() 方法返回一個UnpooledDataSource 實例對象引用,我們假定為dataSource

    ? 使用 UnpooledDataSourcegetConnection() ,每調(diào)用一次就會產(chǎn)生一個新的 Connection 實例對象。UnPooledDataSourcegetConnection() 方法實現(xiàn)如下:

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. 初始化驅(qū)動: 判斷driver驅(qū)動是否已經(jīng)加載到內(nèi)存中,如果還沒有加載,則會動態(tài)地加載driver類,并實例化一個Driver對象,使用DriverManager.registerDriver()方法將其注冊到內(nèi)存中,以供后續(xù)使用。

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

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

  4. 返回Connection對象

從上述的代碼中可以看到,我們每調(diào)用一次getConnection()方法,都會通過DriverManager.getConnection()返回新的java.sql.Connection實例,所以沒有連接池。
  • POOLED 數(shù)據(jù)源 連接池

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

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

activeConnections: 活動(active)狀態(tài)的PooledConnection對象被放置到名為activeConnectionsArrayList中,表示當前正在被使用的PooledConnection集合,調(diào)用PooledDataSourcegetConnection()方法時,會優(yōu)先從idleConnections集合中取PooledConnection對象,如果沒有,則看此集合是否已滿,如果未滿,PooledDataSource會創(chuàng)建出一個PooledConnection,添加到此集合中,并返回

現(xiàn)在讓我們看一下popConnection()方法到底做了什么:

  1. 先看是否有空閑(idle)狀態(tài)下的PooledConnection對象,如果有,就直接返回一個可用的PooledConnection對象;否則進行第2步。
  1. 查看活動狀態(tài)的PooledConnectionactiveConnections是否已滿;如果沒有滿,則創(chuàng)建一個新的PooledConnection對象,然后放到activeConnections池中,然后返回此PooledConnection對象;否則進行第三步;
  1. 看最先進入activeConnections池中的PooledConnection對象是否已經(jīng)過期:如果已經(jīng)過期,從activeConnections池中移除此對象,然后創(chuàng)建一個新的PooledConnection對象,添加到activeConnections中,然后將此對象返回;否則進行第4步。

  2. 線程等待,循環(huán)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
            {
                // 連接池中沒有空閑連接,則取當前正在使用的連接數(shù)小于最大限定值,
                if (state.activeConnections.size() < poolMaximumActiveConnections)
                {
                    // 創(chuàng)建一個新的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 當活動連接池已滿,不能創(chuàng)建時,取出活動連接池的第一個,即最先進入連接池的PooledConnection對象
                    // 計算它的校驗時間,如果校驗時間大于連接池規(guī)定的最大校驗時間,則認為它已經(jīng)過期了,利用這個PoolConnection內(nèi)部的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對象時,如果不使用數(shù)據(jù)庫連接池,我們一般會調(diào)用 connection.close() 方法,關(guān)閉connection連接,釋放資源

調(diào)用過close()方法的Connection對象所持有的資源會被全部釋放掉,Connection對象也就不能再使用。那么,如果我們使用了連接池,我們在用完了Connection對象時,需要將它放在連接池中,該怎樣做呢?

? 可能大家第一個在腦海里閃現(xiàn)出來的想法就是:我在應(yīng)該調(diào)用con.close()方法的時候,不調(diào)用close()方法,將其換成將Connection對象放到連接池容器中的代碼!

怎樣實現(xiàn)Connection對象調(diào)用了close()方法,而實際是將其添加到連接池中

? 這是要使用代理模式,為真正的Connection對象創(chuàng)建一個代理對象,代理對象所有的方法都是調(diào)用相應(yīng)的真正Connection對象的方法實現(xiàn)。當代理對象執(zhí)行close()方法時,要特殊處理,不調(diào)用真正Connection對象的close()方法,而是將Connection對象添加到連接池中。

MyBatisPooledDataSourcePoolState內(nèi)部維護的對象是PooledConnection類型的對象,而PooledConnection則是對真正的數(shù)據(jù)庫連接java.sql.Connection實例對象的包裹器。

PooledConnection對象內(nèi)持有一個真正的數(shù)據(jù)庫連接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);
            }
        }
    }
}
掃碼關(guān)注公眾號:java之旅
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容