Mybatis技術內幕(2.4):資料來源模組

失控的阿甘發表於2019-04-03

基於Mybatis-3.5.0版本

1.0 資料來源模組

  在資料持久層中,資料來源是一個非常重要的元件,期效能直接關係到整個資料持久層的效能。在實踐中比較常見的第三方資料來源元件有DBCP、C3P0、阿里druid和Springboot2推薦的HikariCP等,Mybatis不僅可以整合第三方資料來源元件,還提供了自己的資料來源實現。

Mybatis資料來源模組包結構如下:

Mybatis技術內幕(2.4):資料來源模組

2.0 DataSource 資料來源介面

  常見的資料來源元件都實現了javax.sql.DataSource介面,Mybatis自身實現的資料來源實現也不例外。Mybatis提供了兩個javax.sql.DataSource介面實現,分別是PooledDataSource和UnpooledDataSource。

Mybatis技術內幕(2.4):資料來源模組

2.1 UnpooledDataSource 非池化DataSource

org.apache.ibatis.datasource.unpooled.UnpooledDataSource實現DataSource介面,非池化的DataSource物件。程式碼如下:

/**
 * 非池化DataSource
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class UnpooledDataSource implements DataSource {
	// Driver 類載入器
	private ClassLoader driverClassLoader;
	// 資料庫驅動的相關配置
	private Properties driverProperties;
	// 快取所有已註冊的資料庫連線驅動
	private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();

	// 資料庫連線的驅動名稱
	private String driver;
	// 資料庫 URL
	private String url;
	// 使用者名稱
	private String username;
	// 密碼
	private String password;

	// 是否自動提交事務
	private Boolean autoCommit;
	// 預設事務隔離級別
	private Integer defaultTransactionIsolationLevel;

	/**
	 * 初始化 registeredDrivers
	 * 將已在DriverManager中註冊的JDBC Driver複製一份
	 */
	static {
		Enumeration<Driver> drivers = DriverManager.getDrivers();
		while (drivers.hasMoreElements()) {
			Driver driver = drivers.nextElement();
			registeredDrivers.put(driver.getClass().getName(), driver);
		}
	}

	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 UnpooledDataSource(String driver, String url, Properties driverProperties) {
		this.driver = driver;
		this.url = url;
		this.driverProperties = driverProperties;
	}

	public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username,
			String password) {
		this.driverClassLoader = driverClassLoader;
		this.driver = driver;
		this.url = url;
		this.username = username;
		this.password = password;
	}

	public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
		this.driverClassLoader = driverClassLoader;
		this.driver = driver;
		this.url = url;
		this.driverProperties = driverProperties;
	}

	@Override
	public Connection getConnection() throws SQLException {
		return doGetConnection(username, password);
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		return doGetConnection(username, password);
	}

	@Override
	public void setLoginTimeout(int loginTimeout) {
		DriverManager.setLoginTimeout(loginTimeout);
	}

	@Override
	public int getLoginTimeout() {
		return DriverManager.getLoginTimeout();
	}

	@Override
	public void setLogWriter(PrintWriter logWriter) {
		DriverManager.setLogWriter(logWriter);
	}

	@Override
	public PrintWriter getLogWriter() {
		return DriverManager.getLogWriter();
	}

	public ClassLoader getDriverClassLoader() {
		return driverClassLoader;
	}

	public void setDriverClassLoader(ClassLoader driverClassLoader) {
		this.driverClassLoader = driverClassLoader;
	}

	public Properties getDriverProperties() {
		return driverProperties;
	}

	public void setDriverProperties(Properties driverProperties) {
		this.driverProperties = driverProperties;
	}

	public String getDriver() {
		return driver;
	}

	public synchronized void setDriver(String driver) {
		this.driver = driver;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Boolean isAutoCommit() {
		return autoCommit;
	}

	public void setAutoCommit(Boolean autoCommit) {
		this.autoCommit = autoCommit;
	}

	public Integer getDefaultTransactionIsolationLevel() {
		return defaultTransactionIsolationLevel;
	}

	public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
		this.defaultTransactionIsolationLevel = defaultTransactionIsolationLevel;
	}

	private Connection doGetConnection(String username, String password) throws SQLException {
		// 建立 Properties 物件
		Properties props = new Properties();
		if (driverProperties != null) {
			// 設定 driverProperties 到 props 中
			props.putAll(driverProperties);
		}
		// 設定 user和password
		if (username != null) {
			props.setProperty("user", username);
		}
		if (password != null) {
			props.setProperty("password", password);
		}
		return doGetConnection(props);
	}

	private Connection doGetConnection(Properties properties) throws SQLException {
		// 初始化 Driver
		initializeDriver();
		// 獲得 Connection 物件
		Connection connection = DriverManager.getConnection(url, properties);
		// 配置 Connection 物件
		configureConnection(connection);
		return connection;
	}

	/**
	 * 初始化 Driver
	 * 
	 * @throws SQLException
	 */
	private synchronized void initializeDriver() throws SQLException {
		// 檢查驅動是否已註冊
		if (!registeredDrivers.containsKey(driver)) {
			Class<?> driverType;
			try {
				// 獲得 driver 類
				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 物件
				Driver driverInstance = (Driver) driverType.newInstance();
				// 建立 DriverProxy物件,並註冊到 DriverManager中
				DriverManager.registerDriver(new DriverProxy(driverInstance));
				// 新增到registeredDrivers中
				registeredDrivers.put(driver, driverInstance);
			} catch (Exception e) {
				throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
			}
		}
	}

	private void configureConnection(Connection conn) throws SQLException {
		// 設定自動提交
		if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
			conn.setAutoCommit(autoCommit);
		}
		// 設定事務隔離級別
		if (defaultTransactionIsolationLevel != null) {
			conn.setTransactionIsolation(defaultTransactionIsolationLevel);
		}
	}

	/**
	 * 靜態代理
	 * @ClassName: DriverProxy  
	 * @Description: 主要是針對getParentLogger方法,使用 MyBatis 自定義的 Logger 物件 
	 * @date 2019年4月3日  
	 *
	 */
	private static class DriverProxy implements Driver {
		private Driver driver;

		DriverProxy(Driver d) {
			this.driver = d;
		}

		@Override
		public boolean acceptsURL(String u) throws SQLException {
			return this.driver.acceptsURL(u);
		}

		@Override
		public Connection connect(String u, Properties p) throws SQLException {
			return this.driver.connect(u, p);
		}

		@Override
		public int getMajorVersion() {
			return this.driver.getMajorVersion();
		}

		@Override
		public int getMinorVersion() {
			return this.driver.getMinorVersion();
		}

		@Override
		public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
			return this.driver.getPropertyInfo(u, p);
		}

		@Override
		public boolean jdbcCompliant() {
			return this.driver.jdbcCompliant();
		}

		// @Override only valid jdk7+
		public Logger getParentLogger() {
			return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
		}
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		throw new SQLException(getClass().getName() + " is not a wrapper.");
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}

	// @Override only valid jdk7+
	public Logger getParentLogger() {
		// requires JDK version 1.6
		return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
	}
}
複製程式碼

2.2 PooledDataSource 池化的DataSource

  資料庫連線的建立過程是非常耗時的,資料庫能夠建立的連線也非常有限,所以在絕大多數系統中,資料庫連線都是非常珍貴的資源,使用資料庫連線池就顯得尤其必要。
  org.apache.ibatis.datasource.pooled.PooledDataSource實現DataSource介面,池化的DataSource實現類。程式碼如下:

public class PooledDataSource implements DataSource {

	private static final Log log = LogFactory.getLog(PooledDataSource.class);

	private final PoolState state = new PoolState(this);
	// UnpooledDataSource物件,用於獲取資料庫連線
	private final UnpooledDataSource dataSource;

	// OPTIONAL CONFIGURATION FIELDS
	// 最大活動連線數(預設為10)
	protected int poolMaximumActiveConnections = 10;
	// 最大空閒連線數(預設為5)
	protected int poolMaximumIdleConnections = 5;
	/**
	 * 最大可回收時間。
	 * 即當達到最大活動連結數時,此時如果有程式獲取連線,則檢查最先使用的連線,看其是否超出了該時間,如果超出了該時間,則可以回收該連線。(預設20s)
	 */
	protected int poolMaximumCheckoutTime = 20000;
	// 沒有連線時,重嘗試獲取連線以及列印日誌的時間間隔(預設20s)
	protected int poolTimeToWait = 20000;
	/**
	 * 這是一個關於壞連線容忍度的底層設定, 作用於每一個嘗試從快取池獲取連線的執行緒.
	 * 如果這個執行緒獲取到的是一個壞的連線,那麼這個資料來源允許這個執行緒嘗試重新獲取一個新的連線,但是這個重新嘗試的次數不應該超過poolMaximumIdleConnections與poolMaximumLocalBadConnectionTolerance之和
	 */
	protected int poolMaximumLocalBadConnectionTolerance = 3;
	// 檢查連線正確的語句,預設為"NO PING QUERY SET",即沒有,使用會導致拋異常
	protected String poolPingQuery = "NO PING QUERY SET";
	// 是否開啟ping檢測,(預設:false)
	protected boolean poolPingEnabled;
	// 設定ping檢測時間間隔,通常用於檢測超時連線(預設為0,即當開啟檢測後每次從連線詞中獲取連線以及放回連線池都需要檢測)
	protected int poolPingConnectionsNotUsedFor;
	
	// 期望 Connection 的型別編碼
	private int expectedConnectionTypeCode;

	public PooledDataSource() {
		dataSource = new UnpooledDataSource();
	}

	public PooledDataSource(UnpooledDataSource dataSource) {
		this.dataSource = dataSource;
	}

	public PooledDataSource(String driver, String url, String username, String password) {
		// 建立 UnpooledDataSource 物件
		dataSource = new UnpooledDataSource(driver, url, username, password);
		// 計算  expectedConnectionTypeCode 的值
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	public PooledDataSource(String driver, String url, Properties driverProperties) {
		dataSource = new UnpooledDataSource(driver, url, driverProperties);
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username,
			String password) {
		dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
		dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	@Override
	public Connection getConnection() throws SQLException {
		return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		return popConnection(username, password).getProxyConnection();
	}

	@Override
	public void setLoginTimeout(int loginTimeout) {
		DriverManager.setLoginTimeout(loginTimeout);
	}

	@Override
	public int getLoginTimeout() {
		return DriverManager.getLoginTimeout();
	}

	@Override
	public void setLogWriter(PrintWriter logWriter) {
		DriverManager.setLogWriter(logWriter);
	}

	@Override
	public PrintWriter getLogWriter() {
		return DriverManager.getLogWriter();
	}

	public void setDriver(String driver) {
		dataSource.setDriver(driver);
		forceCloseAll();
	}

	public void setUrl(String url) {
		dataSource.setUrl(url);
		forceCloseAll();
	}

	public void setUsername(String username) {
		dataSource.setUsername(username);
		forceCloseAll();
	}

	public void setPassword(String password) {
		dataSource.setPassword(password);
		forceCloseAll();
	}

	public void setDefaultAutoCommit(boolean defaultAutoCommit) {
		dataSource.setAutoCommit(defaultAutoCommit);
		forceCloseAll();
	}

	public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
		dataSource.setDefaultTransactionIsolationLevel(defaultTransactionIsolationLevel);
		forceCloseAll();
	}

	public void setDriverProperties(Properties driverProps) {
		dataSource.setDriverProperties(driverProps);
		forceCloseAll();
	}

	/**
	 * The maximum number of active connections
	 *
	 * @param poolMaximumActiveConnections The maximum number of active connections
	 */
	public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
		this.poolMaximumActiveConnections = poolMaximumActiveConnections;
		forceCloseAll();
	}

	/**
	 * The maximum number of idle connections
	 *
	 * @param poolMaximumIdleConnections The maximum number of idle connections
	 */
	public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
		this.poolMaximumIdleConnections = poolMaximumIdleConnections;
		forceCloseAll();
	}

	/**
	 * The maximum number of tolerance for bad connection happens in one thread
	 * which are applying for new {@link PooledConnection}
	 *
	 * @param poolMaximumLocalBadConnectionTolerance max tolerance for bad
	 *                                               connection happens in one
	 *                                               thread
	 *
	 * @since 3.4.5
	 */
	public void setPoolMaximumLocalBadConnectionTolerance(int poolMaximumLocalBadConnectionTolerance) {
		this.poolMaximumLocalBadConnectionTolerance = poolMaximumLocalBadConnectionTolerance;
	}

	/**
	 * The maximum time a connection can be used before it *may* be given away
	 * again.
	 *
	 * @param poolMaximumCheckoutTime The maximum time
	 */
	public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
		this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
		forceCloseAll();
	}

	/**
	 * The time to wait before retrying to get a connection
	 *
	 * @param poolTimeToWait The time to wait
	 */
	public void setPoolTimeToWait(int poolTimeToWait) {
		this.poolTimeToWait = poolTimeToWait;
		forceCloseAll();
	}

	/**
	 * The query to be used to check a connection
	 *
	 * @param poolPingQuery The query
	 */
	public void setPoolPingQuery(String poolPingQuery) {
		this.poolPingQuery = poolPingQuery;
		forceCloseAll();
	}

	/**
	 * Determines if the ping query should be used.
	 *
	 * @param poolPingEnabled True if we need to check a connection before using it
	 */
	public void setPoolPingEnabled(boolean poolPingEnabled) {
		this.poolPingEnabled = poolPingEnabled;
		forceCloseAll();
	}

	/**
	 * If a connection has not been used in this many milliseconds, ping the
	 * database to make sure the connection is still good.
	 *
	 * @param milliseconds the number of milliseconds of inactivity that will
	 *                     trigger a ping
	 */
	public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
		this.poolPingConnectionsNotUsedFor = milliseconds;
		forceCloseAll();
	}

	public String getDriver() {
		return dataSource.getDriver();
	}

	public String getUrl() {
		return dataSource.getUrl();
	}

	public String getUsername() {
		return dataSource.getUsername();
	}

	public String getPassword() {
		return dataSource.getPassword();
	}

	public boolean isAutoCommit() {
		return dataSource.isAutoCommit();
	}

	public Integer getDefaultTransactionIsolationLevel() {
		return dataSource.getDefaultTransactionIsolationLevel();
	}

	public Properties getDriverProperties() {
		return dataSource.getDriverProperties();
	}

	public int getPoolMaximumActiveConnections() {
		return poolMaximumActiveConnections;
	}

	public int getPoolMaximumIdleConnections() {
		return poolMaximumIdleConnections;
	}

	public int getPoolMaximumLocalBadConnectionTolerance() {
		return poolMaximumLocalBadConnectionTolerance;
	}

	public int getPoolMaximumCheckoutTime() {
		return poolMaximumCheckoutTime;
	}

	public int getPoolTimeToWait() {
		return poolTimeToWait;
	}

	public String getPoolPingQuery() {
		return poolPingQuery;
	}

	public boolean isPoolPingEnabled() {
		return poolPingEnabled;
	}

	public int getPoolPingConnectionsNotUsedFor() {
		return poolPingConnectionsNotUsedFor;
	}

	/*
	 * Closes all active and idle connections in the pool
	 */
	public void forceCloseAll() {
		synchronized (state) {
			expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
					dataSource.getPassword());
			for (int i = state.activeConnections.size(); i > 0; i--) {
				try {
					PooledConnection conn = state.activeConnections.remove(i - 1);
					conn.invalidate();

					Connection realConn = conn.getRealConnection();
					if (!realConn.getAutoCommit()) {
						realConn.rollback();
					}
					realConn.close();
				} catch (Exception e) {
					// ignore
				}
			}
			for (int i = state.idleConnections.size(); i > 0; i--) {
				try {
					PooledConnection conn = state.idleConnections.remove(i - 1);
					conn.invalidate();

					Connection realConn = conn.getRealConnection();
					if (!realConn.getAutoCommit()) {
						realConn.rollback();
					}
					realConn.close();
				} catch (Exception e) {
					// ignore
				}
			}
		}
		if (log.isDebugEnabled()) {
			log.debug("PooledDataSource forcefully closed/removed all connections.");
		}
	}

	public PoolState getPoolState() {
		return state;
	}

	private int assembleConnectionTypeCode(String url, String username, String password) {
		return ("" + url + username + password).hashCode();
	}

	protected void pushConnection(PooledConnection conn) throws SQLException {
		synchronized (state) {
			// 從activeConnections集合中移除該PooledConnection物件
			state.activeConnections.remove(conn);
			if (conn.isValid()) {// 檢測PooledConnection物件是否有效
				// 檢測空閒連線數是否已達到上限,以及PooledConnection是否為該連線池的連線
				if (state.idleConnections.size() < poolMaximumIdleConnections
						&& conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
					// 累積checkout時長
					state.accumulatedCheckoutTime += conn.getCheckoutTime();
					if (!conn.getRealConnection().getAutoCommit()) {// 回滾未提交的事務
						conn.getRealConnection().rollback();
					}
					// 為返回的連線建立新的PooledConnection物件
					PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
					state.idleConnections.add(newConn);// 新增到idleConnections集合
					newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
					newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
					conn.invalidate();// 將原PooledConnection物件設定為無效
					if (log.isDebugEnabled()) {
						log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
					}
					state.notifyAll();// 喚醒阻塞等待的執行緒
				} else {
					// 累積checkout時長
					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();// 將原PooledConnection物件設定為無效
				}
			} else {
				if (log.isDebugEnabled()) {
					log.debug("A bad connection (" + conn.getRealHashCode()
							+ ") attempted to return to the pool, discarding connection.");
				}
				state.badConnectionCount++;
			}
		}
	}

	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.isEmpty()) {// 檢測空閒連線
					// 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物件
						conn = new PooledConnection(dataSource.getConnection(), this);
						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;
							// 將超時列檢移出activeConnections集合
							state.activeConnections.remove(oldestActiveConnection);
							// 如果超時連線未提交,則自動回滾
							if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
								try {
									oldestActiveConnection.getRealConnection().rollback();
								} catch (SQLException e) {
									/*
									 * Just log a message for debug and continue to execute the following statement
									 * like nothing happened. Wrap the bad connection with a new PooledConnection,
									 * this will help to not interrupt current executing thread and give current
									 * thread a chance to join the next competition for another valid/good database
									 * connection. At the end of this loop, bad {@link @conn} will be set as null.
									 */
									log.debug("Bad connection. Could not roll back");
								}
							}
							// 建立新PooledConnection物件,但是真正的資料庫連線並未建立新的
							conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
							conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
							conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
							// 將超時PooledConnection設定為無效
							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;
							}
						}
					}
				}
				if (conn != null) {
					// ping to server and check the connection is valid or not
					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());
						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置空,繼續獲取
						conn = null;
						// 如果超過最大次數,丟擲 SQLException 異常
						if (localBadConnectionCount > (poolMaximumIdleConnections
								+ poolMaximumLocalBadConnectionTolerance)) {
							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;
	}

	/**
	 * Method to check to see if a connection is still usable
	 *
	 * @param conn - the connection to check
	 * @return True if the connection is still usable
	 */
	protected boolean pingConnection(PooledConnection conn) {
		boolean result = true;// 記錄ping操作是否成功

		try {
			//檢測真正的資料庫連線是否已關閉
			result = !conn.getRealConnection().isClosed();
		} catch (SQLException e) {
			if (log.isDebugEnabled()) {
				log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
			}
			result = false;
		}

		if (result) {
			if (poolPingEnabled) {// 檢測poolPingEnabled設定,是否執行執行測試SQL語句
				// 長時間未使用的連線,才需要ping操作來檢測資料庫連線是否正常
				if (poolPingConnectionsNotUsedFor >= 0
						&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
					try {
						if (log.isDebugEnabled()) {
							log.debug("Testing connection " + conn.getRealHashCode() + " ...");
						}
						// 執行測試SQL語句
						Connection realConn = conn.getRealConnection();
						try (Statement statement = realConn.createStatement()) {
							statement.executeQuery(poolPingQuery).close();
						}
						if (!realConn.getAutoCommit()) {
							realConn.rollback();
						}
						result = true;
						if (log.isDebugEnabled()) {
							log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
						}
					} catch (Exception e) {
						log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
						try {
							conn.getRealConnection().close();
						} catch (Exception e2) {
							// ignore
						}
						result = false;
						if (log.isDebugEnabled()) {
							log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
						}
					}
				}
			}
		}
		return result;
	}

	/**
	 * Unwraps a pooled connection to get to the 'real' connection
	 *
	 * @param conn - the pooled connection to unwrap
	 * @return The 'real' connection
	 */
	public static Connection unwrapConnection(Connection conn) {
		if (Proxy.isProxyClass(conn.getClass())) {
			InvocationHandler handler = Proxy.getInvocationHandler(conn);
			if (handler instanceof PooledConnection) {
				return ((PooledConnection) handler).getRealConnection();
			}
		}
		return conn;
	}

	protected void finalize() throws Throwable {
		forceCloseAll();
		super.finalize();
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		throw new SQLException(getClass().getName() + " is not a wrapper.");
	}

	public boolean isWrapperFor(Class<?> iface) {
		return false;
	}

	public Logger getParentLogger() {
		return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); // requires JDK version 1.6
	}
}
複製程式碼

popConnection方法獲取連線,流程如下:

Mybatis技術內幕(2.4):資料來源模組

pushConnection方法關閉連線,流程如下:

Mybatis技術內幕(2.4):資料來源模組

2.2.1 PooledConnection

  從PooledDataSource並不會直接管理java.sql.Connection物件,而是管理PooledConnection物件。在PooledConnection中封裝了真正的資料庫連線物件以及其代理物件,這裡的代理物件是通過JDK動態代理產生的。 org.apache.ibatis.datasource.pooled.PooledConnection實現 InvocationHandler 介面,池化的Connection物件,程式碼如下:

/**
 * @author Clinton Begin
 */
class PooledConnection implements InvocationHandler {

	// 關閉 Connection 方法名
	private static final String CLOSE = "close";
	// JDK Proxy 的介面
	private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };

	private final int hashCode;
	/**
	 * 所屬的 PooledDataSource物件
	 * 該PooledConnection是從該PooledDataSource中獲取的;
	 * 當呼叫close()方法時會將該PooledConnection放回該PooledDataSource中
	 */
	private final PooledDataSource dataSource;
	// 真正的資料庫連線
	private final Connection realConnection;
	// 資料庫連線的代理物件
	private final Connection proxyConnection;
	// 從連線池中取出該連線的時間戳
	private long checkoutTimestamp;
	// 該連線建立的時間戳
	private long createdTimestamp;
	// 最後一次使用的時間戳
	private long lastUsedTimestamp;
	// 由資料庫URL、使用者名稱和密碼計算出來的hash值,用於標識該連線所在的連線池
	private int connectionTypeCode;
	/**
	 * 檢測當前PooledConnection是否有效
	 * 防止程式通過close()方法將連線歸還給連線池之後,依然通過該連線運算元據庫
	 */
	private boolean valid;

	/**
	 * Constructor for SimplePooledConnection that uses the Connection and
	 * PooledDataSource passed in
	 *
	 * @param connection - the connection that is to be presented as a pooled
	 *                   connection
	 * @param dataSource - the dataSource that the connection is from
	 */
	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;
		// 建立代理的 Connection 物件
		this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
	}

	/**
	 * Invalidates the connection
	 */
	public void invalidate() {
		valid = false;
	}

	/**
	 * Method to see if the connection is usable
	 *
	 * @return True if the connection is usable
	 */
	public boolean isValid() {
		return valid && realConnection != null && dataSource.pingConnection(this);
	}

	/**
	 * Getter for the *real* connection that this wraps
	 *
	 * @return The connection
	 */
	public Connection getRealConnection() {
		return realConnection;
	}

	/**
	 * Getter for the proxy for the connection
	 *
	 * @return The proxy
	 */
	public Connection getProxyConnection() {
		return proxyConnection;
	}

	/**
	 * Gets the hashcode of the real connection (or 0 if it is null)
	 *
	 * @return The hashcode of the real connection (or 0 if it is null)
	 */
	public int getRealHashCode() {
		return realConnection == null ? 0 : realConnection.hashCode();
	}

	/**
	 * Getter for the connection type (based on url + user + password)
	 *
	 * @return The connection type
	 */
	public int getConnectionTypeCode() {
		return connectionTypeCode;
	}

	/**
	 * Setter for the connection type
	 *
	 * @param connectionTypeCode - the connection type
	 */
	public void setConnectionTypeCode(int connectionTypeCode) {
		this.connectionTypeCode = connectionTypeCode;
	}

	/**
	 * Getter for the time that the connection was created
	 *
	 * @return The creation timestamp
	 */
	public long getCreatedTimestamp() {
		return createdTimestamp;
	}

	/**
	 * Setter for the time that the connection was created
	 *
	 * @param createdTimestamp - the timestamp
	 */
	public void setCreatedTimestamp(long createdTimestamp) {
		this.createdTimestamp = createdTimestamp;
	}

	/**
	 * Getter for the time that the connection was last used
	 *
	 * @return - the timestamp
	 */
	public long getLastUsedTimestamp() {
		return lastUsedTimestamp;
	}

	/**
	 * Setter for the time that the connection was last used
	 *
	 * @param lastUsedTimestamp - the timestamp
	 */
	public void setLastUsedTimestamp(long lastUsedTimestamp) {
		this.lastUsedTimestamp = lastUsedTimestamp;
	}

	/**
	 * Getter for the time since this connection was last used
	 *
	 * @return - the time since the last use
	 */
	public long getTimeElapsedSinceLastUse() {
		return System.currentTimeMillis() - lastUsedTimestamp;
	}

	/**
	 * Getter for the age of the connection
	 *
	 * @return the age
	 */
	public long getAge() {
		return System.currentTimeMillis() - createdTimestamp;
	}

	/**
	 * Getter for the timestamp that this connection was checked out
	 *
	 * @return the timestamp
	 */
	public long getCheckoutTimestamp() {
		return checkoutTimestamp;
	}

	/**
	 * Setter for the timestamp that this connection was checked out
	 *
	 * @param timestamp the timestamp
	 */
	public void setCheckoutTimestamp(long timestamp) {
		this.checkoutTimestamp = timestamp;
	}

	/**
	 * Getter for the time that this connection has been checked out
	 *
	 * @return the time
	 */
	public long getCheckoutTime() {
		return System.currentTimeMillis() - checkoutTimestamp;
	}

	@Override
	public int hashCode() {
		return hashCode;
	}

	/**
	 * Allows comparing this connection to another
	 *
	 * @param obj - the other connection to test for equality
	 * @see Object#equals(Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof PooledConnection) {
			return realConnection.hashCode() == ((PooledConnection) obj).realConnection.hashCode();
		} else if (obj instanceof Connection) {
			return hashCode == obj.hashCode();
		} else {
			return false;
		}
	}

	/**
	 * 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[])
	 */
	@Override
	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;
		}
		try {
			// 如果不是Object類的方法
			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);
		}
	}

	private void checkConnection() throws SQLException {
		if (!valid) {
			throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
		}
	}
}
複製程式碼

失控的阿甘,樂於分享,記錄點滴

相關文章