1.概念介紹
1.1 數據源:顧名思義,數據的來源,它包含了數據庫類型信息,位置和數據等信息,一個數據源對應一個數據庫。
1.2 連接池:在做持久化操作時,需要通過數據庫連接對象來連接數據庫,而連接池就是數據庫連接對象的緩沖池,需要的時候可以從這個緩沖池中直接取出。
1.3 數據源的分類:UnpooledDataSource,PooledDataSource和JndiDataSourceFactory,采用的是工廠模式來生成這些對象的,如下如所示:
2.DataSource的創建過程
數據源DataSource是在mybatis初始化加載配置文件的時候進行創建的,配置文件信息如下:
<environments default="development"> <environment id="development"> <transactionManager type="JDBC" />
// type="POOLED" 表示創建有連接池的數據源 PooledDataSource對象
// type="UNPOOLED" 表示創建沒有連接池的數據源 UnpooledDataSource對象
// type="JNDI" 表示會從JNDI服務器上查找數據源 <dataSource type="POOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment> </environments>
創建過程,看源碼,進入XMLConfigBuilder類的dataSourceElement方法:
private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) {
// 獲取type的屬性值 String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties();
// 根據type的屬性值,獲取DataSourceFactory的class對象,然后根據DataSourceFactory的class對象創建一個DataSourceFactory實例對象。 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
// 設置DataSource的相關屬性 進入該方法 factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }
當DataSourceFactory實例對象創建完成以后,此時對應的DataSource也就創建完成了,通過源碼可知,DataSource的創建是在DataSourceFactory的構造器中執行的。比如,看下UnpooledDataSourceFactory的構造方法:
public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); }
進入 factory.setProperties(props);方法:
public void setProperties(Properties properties) { Properties driverProperties = new Properties();
// 創建DataSource對應的MetaObject對象 MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
// 遍歷Properties集合,該集合中配置了數據源需要的信息 for (Object key : properties.keySet()) { String propertyName = (String) key; if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) { String value = properties.getProperty(propertyName); driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value); } else if (metaDataSource.hasSetter(propertyName)) { String value = (String) properties.get(propertyName);
// 進入該方法可知:對屬性類型進行類型轉換,主要是Integer,Long,Boolean的類型轉換 Object convertedValue = convertValue(metaDataSource, propertyName, value);
// 設置DataSource的相關屬性 metaDataSource.setValue(propertyName, convertedValue); } else { throw new DataSourceException("Unknown DataSource property: " + propertyName); } } if (driverProperties.size() > 0) { metaDataSource.setValue("driverProperties", driverProperties); } }
這就是DataSouece對象的創建及其屬性配置的過程。
3.UnpooledDataSource的解析
UnpooledDataSource實現了DataSource接口,每次調用它的getConnection方法時,都會創建一個新的連接。接下來對這個類進行分析:
在該類中有一個靜態代碼塊如下所示,就是在加載UnpooledDataSource類時,會加載該靜態代碼塊,將已經在DriverManager中注冊的JDBC Driver復制一份到UnpooledDataSource.registeredDrivers集合中。
static { Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } }
3.1 屬性:
private ClassLoader driverClassLoader; // 加載Driver類的類加載器 private Properties driverProperties; // 數據庫連接驅動的相關配置
// 緩存所有已經注冊的數據庫連接驅動 private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>(); private String driver; //數據庫連接的驅動名稱 private String url; private String username; private String password; private Boolean autoCommit; // 是否自動提交 private Integer defaultTransactionIsolationLevel; //事務的隔離級別
3.2 獲取連接對象Connection的過程:
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化數據庫驅動 initializeDriver();
// 創建數據庫連接對象 Connection connection = DriverManager.getConnection(url, properties); // 配置連接對象的隔離級別和是否自動提交
configureConnection(connection); return connection; }
進入initializeDriver方法:
private synchronized void initializeDriver() throws SQLException {
// 判斷數據庫驅動是否已經被加載,如果沒有則動態加載 if (!registeredDrivers.containsKey(driver)) { Class<?> driverType; try { if (driverClassLoader != null) { driverType = Class.forName(driver, true, driverClassLoader); } else { driverType = Resources.classForName(driver); } // DriverManager requires the driver to be loaded via the system ClassLoader. // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
// 獲取Driver類的實例對象,然后注冊到DriverManager中
Driver driverInstance = (Driver)driverType.newInstance(); DriverManager.registerDriver(new DriverProxy(driverInstance));
// 將driver添加到registeredDriver集合中
registeredDrivers.put(driver, driverInstance); } catch (Exception e) { throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e); } } }
每調用一次getConnection方法,上面的流程都會重新走一次,所以每次都是獲取的新的Connection連接對象。
4.PooledDataSource的解析
在進行PooledDataSource的講解前,先分析PooledConnection和PoolState這兩個類,因為它們兩個對PooledDataSource而言很重要。
4.1 PooledConnection
PooledDataSource並不會直接管理java.sql.Connection對象,而是管理PooledConnection對象,該對象封裝了真正的數據庫連接對象java.sql.Connection和它自己的代理對象。
// 實現了InvocationHandler接口,所以連接對象的代理對象proxyConnection調用某個方法時,真正執行的邏輯是在invoke方法
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; // 根據數據庫連接的URL,用戶名和密碼生成的hash值,用於標識該連接所在的連接池 private boolean valid; //檢測當前連接是否有效
/* * Required for InvocationHandler implementation. * * @param proxy - not used * @param method - the method to be executed * @param args - the parameters to be passed to the method * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[]) */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName();
// 如果調用的是close方法,那么把該連接對象放到連接池中 if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { dataSource.pushConnection(this); return null; } else { try { if (!Object.class.equals(method.getDeclaringClass())) { // issue #579 toString() should never fail // throw an SQLException instead of a Runtime checkConnection(); }
// 調用真正的連接對象對應的方法 return method.invoke(realConnection, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } }
4.2 PoolState
PoolState是用於管理PooledConnection對象狀態的組件,它有兩個屬性集合,來管理空閑狀態的連接和活躍狀態的連接,看源碼:
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); // 空閑的PooledConnection集合 protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); // 活躍的PooledConnection集合 protected long requestCount = 0; // 請求數據庫連接的次數 protected long accumulatedRequestTime = 0; // 獲取連接的積累時間 protected long accumulatedCheckoutTime = 0; // 所有連接積累的checkoutTime時長 CheckoutTime 是指從連接池中取出連接到歸還連接着段時長 protected long claimedOverdueConnectionCount = 0; // 當連接長時間未歸還給連接池時,會被認為連接超時,這個字段就是表示連接超時的個數 protected long accumulatedCheckoutTimeOfOverdueConnections = 0;// 累計超時時間 protected long accumulatedWaitTime = 0;// 累計等待時間 protected long hadToWaitCount = 0;// 累計等待次數 protected long badConnectionCount = 0;// 無效的連接數
4.3 PooledDataSource 是一個實現了簡單連接池的數據源,我們通過源碼來對它進行學習:
4.3.1 屬性
// 用於管理連接池的狀態並記錄統計信息 稍后會對它進行分析
private final PoolState state = new PoolState(this); // 用於生成真實的數據庫連接對象,在構造函數中初始化該字段 private final UnpooledDataSource dataSource; // OPTIONAL CONFIGURATION FIELDS protected int poolMaximumActiveConnections = 10; // 最大活躍連接數 protected int poolMaximumIdleConnections = 5; // 最大空閑連接數 protected int poolMaximumCheckoutTime = 20000; // 從連接池中取出該連接的最大時長 protected int poolTimeToWait = 20000; // 在無法獲取連接時,線程需要等待的時間 protected String poolPingQuery = "NO PING QUERY SET"; // 在檢測一個數據庫連接是否可用時,會給數據庫發送一個測試SQL語句 protected boolean poolPingEnabled = false; // 是否允許發送測試SQL語句 protected int poolPingConnectionsNotUsedFor = 0; // 根據數據庫的URL,用戶名和密碼生成的一個hash值,標識着當前的連接池,在構造函數中初始化 private int expectedConnectionTypeCode;
4.2 獲取連接的過程:
先思考一個問題:下面這個方法什么時候執行呢?其實在調用查詢語句goodsMapper.selectGoodsById("1");時,才會去獲取連接的。
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) { // Pool has available connection conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else {
// 如果沒有,那么判斷活躍的連接對象的個數是否超過允許的最大值 // Pool does not have available connection if (state.activeConnections.size() < poolMaximumActiveConnections) { // Can create new connection // 如果沒有,則創建一個PooledConnection,它是對真實的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 oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) { //檢測該連接是否超時 // Can claim overdue connection 對超時的連接信息進行統計 state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; // 將超時連接移除activeCollections集合
state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { oldestActiveConnection.getRealConnection().rollback(); }
// 創建新的PooledCOnnection,但是真實的連接對象並未創建新的,是剛才移除的那個真實的連接對象 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()) { // 判斷PooledCOnnection是否有效 if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); }
// 設置PooledConnection的相關屬性 conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis());
// 將該PooledCollection放到activeConnections集合中 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; }
4.3.3 把連接放回到連接池的過程:
protected void pushConnection(PooledConnection conn) throws SQLException { synchronized (state) { //同步 state.activeConnections.remove(conn); // 從activeConnections中移除該連接對象 if (conn.isValid()) { // 驗證該連接是否有效 // 判斷空閑連接數是否達到上限,以及該連接是否屬於該連接池
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) { state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 累計連接時長 if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); }
// 創建一個連接對象,真正的連接不是新建的 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), 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."); } state.notifyAll();// 喚醒阻塞等待的線程 } else {
// 如果空閑連接數達到上限,或者 該連接對象不屬於該連接池 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."); } state.badConnectionCount++; } } }