在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
UNPOOLED
不使用鏈接池的數據源,當 dateSource
的type屬性被配置成了UNPOOLED
,MyBatis
首先會實例化一個UnpooledDataSourceFactory
工廠實例,而後經過 .getDataSource()
方法返回一個UnpooledDataSource
實例對象引用,咱們假定爲dataSource
。框架
使用 UnpooledDataSource
的 getConnection()
,每調用一次就會產生一個新的 Connection
實例對象。UnPooledDataSource
的 getConnection()
方法實現以下:性能
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
會作如下事情:優化
driver
類,並實例化一個Driver
對象,使用DriverManager.registerDriver()
方法將其註冊到內存中,以供後續使用。DriverManager.getConnection()
方法建立鏈接。autoCommit
和隔離級別isolationLevel
。從上述的代碼中能夠看到,咱們每調用一次
getConnection()
方法,都會經過DriverManager.getConnection()
返回新的java.sql.Connection
實例,因此沒有鏈接池。
POOLED
數據源 鏈接池PooledDataSource: 將java.sql.Connection
對象包裹成PooledConnection
對象放到了PoolState
類型的容器中維護。 MyBatis
將鏈接池中的PooledConnection
分爲兩種狀態: 空閒狀態(idle)和活動狀態(active),這兩種狀態的PooledConnection
對象分別被存儲到PoolState
容器內的idleConnections
和activeConnections
兩個List集合中:this
idleConnections: 空閒(idle)狀態PooledConnection
對象被放置到此集合中,表示當前閒置的沒有被使用的PooledConnection
集合,調用PooledDataSource
的getConnection()
方法時,會優先今後集合中取PooledConnection
對象。當用完一個java.sql.Connection對象時,MyBatis
會將其包裹成PooledConnection
對象放到此集合中。url
activeConnections: 活動(active)狀態的PooledConnection
對象被放置到名爲activeConnections
的ArrayList
中,表示當前正在被使用的PooledConnection
集合,調用PooledDataSource
的getConnection()
方法時,會優先從idleConnections
集合中取PooledConnection
對象,若是沒有,則看此集合是否已滿,若是未滿,PooledDataSource
會建立出一個PooledConnection
,添加到此集合中,並返回
如今讓咱們看一下popConnection()
方法到底作了什麼:
PooledConnection
對象,若是有,就直接返回一個可用的PooledConnection
對象;不然進行第2步。PooledConnection
池activeConnections
是否已滿;若是沒有滿,則建立一個新的PooledConnection
對象,而後放到activeConnections
池中,而後返回此PooledConnection
對象;不然進行第三步;activeConnections
池中的PooledConnection
對象是否已通過期:若是已通過期,從activeConnections
池中移除此對象,而後建立一個新的PooledConnection
對象,添加到activeConnections
中,而後將此對象返回;不然進行第4步。/* * 傳遞一個用戶名和密碼,從鏈接池中返回可用的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
對象添加到鏈接池中。
MyBatis
的PooledDataSource
的PoolState
內部維護的對象是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); } } } }