如何在MyBatis中實(shí)現(xiàn)DataSource
一、DataSource
首先大家要清楚DataSource屬于MyBatis三層架構(gòu)設(shè)計(jì)的基礎(chǔ)層
然后我們來看看具體的實(shí)現(xiàn)。
在數(shù)據(jù)持久層中,數(shù)據(jù)源是一個(gè)非常重要的組件,其性能直接關(guān)系到整個(gè)數(shù)據(jù)持久層的性能,在實(shí)際開發(fā)中我們常用的數(shù)據(jù)源有 Apache Common DBCP,C3P0,Druid 等,MyBatis不僅可以集成第三方數(shù)據(jù)源,還提供的有自己實(shí)現(xiàn)的數(shù)據(jù)源。
在MyBatis中提供了兩個(gè) javax.sql.DataSource 接口的實(shí)現(xiàn),分別是 PooledDataSource 和 UnpooledDataSource .

二、DataSourceFactory
DataSourceFactory是用來創(chuàng)建DataSource對象的,接口中聲明了兩個(gè)方法,作用如下
public interface DataSourceFactory {
// 設(shè)置 DataSource 的相關(guān)屬性,一般緊跟在初始化完成之后
void setProperties(Properties props);
// 獲取 DataSource 對象
DataSource getDataSource();
}
DataSourceFactory接口的兩個(gè)具體實(shí)現(xiàn)是 UnpooledDataSourceFactory 和 PooledDataSourceFactory 這兩個(gè)工廠對象的作用通過名稱我們也能發(fā)現(xiàn)是用來創(chuàng)建不帶連接池的數(shù)據(jù)源對象和創(chuàng)建帶連接池的數(shù)據(jù)源對象,先來看下 UnpooledDataSourceFactory 中的方法
/**
* 完成對 UnpooledDataSource 的配置
* @param properties 封裝的有 DataSource 所需要的相關(guān)屬性信息
*/
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
// 創(chuàng)建 DataSource 對應(yīng)的 MetaObject 對象
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
// 遍歷 Properties 集合,該集合中配置了數(shù)據(jù)源需要的信息
for (Object key : properties.keySet()) {
String propertyName = (String) key; // 獲取屬性名稱
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
// 以 "driver." 開頭的配置項(xiàng)是對 DataSource 的配置
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
// 有該屬性的 setter 方法
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
// 設(shè)置 DataSource 的相關(guān)屬性值
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
// 設(shè)置 DataSource.driverProperties 的屬性值
metaDataSource.setValue("driverProperties", driverProperties);
}
}
UnpooledDataSourceFactory的getDataSource方法實(shí)現(xiàn)比較簡單,直接返回DataSource屬性記錄的 UnpooledDataSource 對象
三、UnpooledDataSource
UnpooledDataSource 是 DataSource接口的其中一個(gè)實(shí)現(xiàn),但是 UnpooledDataSource 并沒有提供數(shù)據(jù)庫連接池的支持,我們來看下他的具體實(shí)現(xiàn)吧
聲明的相關(guān)屬性信息
private ClassLoader driverClassLoader; // 加載Driver的類加載器 private Properties driverProperties; // 數(shù)據(jù)庫連接驅(qū)動的相關(guān)信息 // 緩存所有已注冊的數(shù)據(jù)庫連接驅(qū)動 private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>(); private String driver; // 驅(qū)動 private String url; // 數(shù)據(jù)庫 url private String username; // 賬號 private String password; // 密碼 private Boolean autoCommit; // 是否自動提交 private Integer defaultTransactionIsolationLevel; // 事務(wù)隔離級別 private Integer defaultNetworkTimeout;
然后在靜態(tài)代碼塊中完成了 Driver的復(fù)制
static {
// 從 DriverManager 中獲取 Drivers
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
// 將獲取的 Driver 記錄到 Map 集合中
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
UnpooledDataSource 中獲取Connection的方法最終都會調(diào)用 doGetConnection() 方法。
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化數(shù)據(jù)庫驅(qū)動
initializeDriver();
// 創(chuàng)建真正的數(shù)據(jù)庫連接
Connection connection = DriverManager.getConnection(url, properties);
// 配置Connection的自動提交和事務(wù)隔離級別
configureConnection(connection);
return connection;
}
四、PooledDataSource
有開發(fā)經(jīng)驗(yàn)的小伙伴都知道,在操作數(shù)據(jù)庫的時(shí)候數(shù)據(jù)庫連接的創(chuàng)建過程是非常耗時(shí)的,數(shù)據(jù)庫能夠建立的連接數(shù)量也是非常有限的,所以數(shù)據(jù)庫連接池的使用是非常重要的,使用數(shù)據(jù)庫連接池會給我們帶來很多好處,比如可以實(shí)現(xiàn)數(shù)據(jù)庫連接的重用,提高響應(yīng)速度,防止數(shù)據(jù)庫連接過多造成數(shù)據(jù)庫假死,避免數(shù)據(jù)庫連接泄漏等等。
首先來看下聲明的相關(guān)的屬性
// 管理狀態(tài) private final PoolState state = new PoolState(this); // 記錄UnpooledDataSource,用于生成真實(shí)的數(shù)據(jù)庫連接對象 private final UnpooledDataSource dataSource; // OPTIONAL CONFIGURATION FIELDS protected int poolMaximumActiveConnections = 10; // 最大活躍連接數(shù) protected int poolMaximumIdleConnections = 5; // 最大空閑連接數(shù) protected int poolMaximumCheckoutTime = 20000; // 最大checkout時(shí)間 protected int poolTimeToWait = 20000; // 無法獲取連接的線程需要等待的時(shí)長 protected int poolMaximumLocalBadConnectionTolerance = 3; // protected String poolPingQuery = "NO PING QUERY SET"; // 測試的SQL語句 protected boolean poolPingEnabled; // 是否允許發(fā)送測試SQL語句 // 當(dāng)連接超過 poolPingConnectionsNotUsedFor毫秒未使用時(shí),會發(fā)送一次測試SQL語句,檢測連接是否正常 protected int poolPingConnectionsNotUsedFor; // 根據(jù)數(shù)據(jù)庫URL,用戶名和密碼生成的一個(gè)hash值。 private int expectedConnectionTypeCode;
然后重點(diǎn)來看下 getConnection 方法,該方法是用來給調(diào)用者提供 Connection 對象的。
@Override
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
我們會發(fā)現(xiàn)其中調(diào)用了 popConnection 方法,在該方法中 返回的是 PooledConnection 對象,而 PooledConnection 對象實(shí)現(xiàn)了 InvocationHandler 接口,所以會使用到Java的動態(tài)代理,其中相關(guān)的屬性為
private static final String CLOSE = "close";
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
private final int hashCode;
private final PooledDataSource dataSource;
// 真正的數(shù)據(jù)庫連接
private final Connection realConnection;
// 數(shù)據(jù)庫連接的代理對象
private final Connection proxyConnection;
private long checkoutTimestamp; // 從連接池中取出該連接的時(shí)間戳
private long createdTimestamp; // 該連接創(chuàng)建的時(shí)間戳
private long lastUsedTimestamp; // 最后一次被使用的時(shí)間戳
private int connectionTypeCode; // 又?jǐn)?shù)據(jù)庫URL、用戶名和密碼計(jì)算出來的hash值,可用于標(biāo)識該連接所在的連接池
// 連接是否有效的標(biāo)志
private boolean valid;
重點(diǎn)關(guān)注下invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.equals(methodName)) {
// 如果是 close 方法被執(zhí)行則將連接放回連接池中,而不是真正的關(guān)閉數(shù)據(jù)庫連接
dataSource.pushConnection(this);
return null;
}
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
// 通過上面的 valid 字段來檢測 連接是否有效
checkConnection();
}
// 調(diào)用真正數(shù)據(jù)庫連接對象的對應(yīng)方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
還有就是前面提到的 PoolState 對象,它主要是用來管理 PooledConnection 對象狀態(tài)的組件,通過兩個(gè) ArrayList 集合分別管理空閑狀態(tài)的連接和活躍狀態(tài)的連接,定義如下:
protected PooledDataSource dataSource; // 空閑的連接 protected final List<PooledConnection> idleConnections = new ArrayList<>(); // 活躍的連接 protected final List<PooledConnection> activeConnections = new ArrayList<>(); protected long requestCount = 0; // 請求數(shù)據(jù)庫連接的次數(shù) protected long accumulatedRequestTime = 0; // 獲取連接累計(jì)的時(shí)間 // CheckoutTime 表示應(yīng)用從連接池中取出來,到歸還連接的時(shí)長 // accumulatedCheckoutTime 記錄了所有連接累計(jì)的CheckoutTime時(shí)長 protected long accumulatedCheckoutTime = 0; // 當(dāng)連接長時(shí)間沒有歸還連接時(shí),會被認(rèn)為該連接超時(shí) // claimedOverdueConnectionCount 記錄連接超時(shí)的個(gè)數(shù) protected long claimedOverdueConnectionCount = 0; // 累計(jì)超時(shí)時(shí)間 protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // 累計(jì)等待時(shí)間 protected long accumulatedWaitTime = 0; // 等待次數(shù) protected long hadToWaitCount = 0; // 無效連接數(shù) protected long badConnectionCount = 0;
再回到 popConnection 方法中來看
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 {// 當(dāng)前連接池 沒有空閑連接
// Pool does not have available connection
if (state.activeConnections.size() < poolMaximumActiveConnections) { // 活躍數(shù)沒有達(dá)到最大連接數(shù) 可以創(chuàng)建新的連接
// Can create new connection 創(chuàng)建新的數(shù)據(jù)庫連接
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else { // 活躍數(shù)已經(jīng)達(dá)到了最大數(shù) 不能創(chuàng)建新的連接
// Cannot create new connection 獲取最先創(chuàng)建的活躍連接
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
// 獲取該連接的超時(shí)時(shí)間
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
// 檢查是否超時(shí)
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection 對超時(shí)連接的信息進(jìn)行統(tǒng)計(jì)
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
// 將超時(shí)連接移除 activeConnections
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
// 如果超時(shí)連接沒有提交 則自動回滾
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");
}
}
// 創(chuàng)建 PooledConnection,但是數(shù)據(jù)庫中的真正連接并沒有創(chuàng)建
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// 將超時(shí)的 PooledConnection 設(shè)置為無效
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// Must wait 無空閑連接,無法創(chuàng)建新連接和無超時(shí)連接 那就只能等待
try {
if (!countedWait) {
state.hadToWaitCount++; // 統(tǒng)計(jì)等待次數(shù)
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait); // 阻塞等待
// 統(tǒng)計(jì)累計(jì)的等待時(shí)間
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
// ping to server and check the connection is valid or not
// 檢查 PooledConnection 是否有效
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 配置 PooledConnection 的相關(guān)屬性
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++; // 進(jìn)行相關(guān)的統(tǒng)計(jì)
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 + 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.");
}
}
}
}
}
為了更好的理解代碼的含義,我們繪制了對應(yīng)的流程圖
然后我們來看下當(dāng)我們從連接池中使用完成了數(shù)據(jù)庫的相關(guān)操作后,是如何來關(guān)閉連接的呢?通過前面的 invoke 方法的介紹其實(shí)我們能夠發(fā)現(xiàn),當(dāng)我們執(zhí)行代理對象的 close 方法的時(shí)候其實(shí)是執(zhí)行的 pushConnection 方法。

具體的實(shí)現(xiàn)代碼為
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
// 從 activeConnections 中移除 PooledConnection 對象
state.activeConnections.remove(conn);
if (conn.isValid()) { // 檢測 連接是否有效
if (state.idleConnections.size() < poolMaximumIdleConnections // 是否達(dá)到上限
&& conn.getConnectionTypeCode() == expectedConnectionTypeCode // 該 PooledConnection 是否為該連接池的連接
) {
state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 累計(jì) checkout 時(shí)長
if (!conn.getRealConnection().getAutoCommit()) { // 回滾未提交的事務(wù)
conn.getRealConnection().rollback();
}
// 為返還連接創(chuàng)建新的 PooledConnection 對象
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
// 添加到 空閑連接集合中
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate(); // 將原來的 PooledConnection 連接設(shè)置為無效
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
// 喚醒阻塞等待的線程
state.notifyAll();
} else { // 空閑連接達(dá)到上限或者 PooledConnection不屬于當(dāng)前的連接池
state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 累計(jì) checkout 時(shí)長
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close(); // 關(guān)閉真正的數(shù)據(jù)庫連接
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate(); // 設(shè)置 PooledConnection 無線
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++; // 統(tǒng)計(jì)無效的 PooledConnection 對象個(gè)數(shù)
}
}
}
為了便于理解,我們同樣的來繪制對應(yīng)的流程圖:

還有就是我們在源碼中多處有看到 conn.isValid方法來檢測連接是否有效
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}
dataSource.pingConnection(this)中會真正的實(shí)現(xiàn)數(shù)據(jù)庫的SQL執(zhí)行操作
最后一點(diǎn)要注意的是在我們修改了任意的PooledDataSource中的屬性的時(shí)候都會執(zhí)行forceCloseAll來強(qiáng)制關(guān)閉所有的連接。

/**
* Closes all active and idle connections in the pool.
*/
public void forceCloseAll() {
synchronized (state) {
// 更新 當(dāng)前的 連接池 標(biāo)識
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(); // 標(biāo)識為無效連接
// 獲取真實(shí)的 數(shù)據(jù)庫連接
Connection realConn = conn.getRealConnection();
if (!realConn.getAutoCommit()) {
realConn.rollback(); // 回滾未處理的事務(wù)
}
realConn.close(); // 關(guān)閉真正的數(shù)據(jù)庫連接
} 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.");
}
}
到此這篇關(guān)于如何在MyBatis中實(shí)現(xiàn)DataSource的文章就介紹到這了,更多相關(guān)DataSource的實(shí)現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用EmbeddedDatabaseBuilder進(jìn)行數(shù)據(jù)庫集成測試
在開發(fā)SpringBoot應(yīng)用程序時(shí),我們通常需要與數(shù)據(jù)庫進(jìn)行交互,為了確保我們的應(yīng)用程序在生產(chǎn)環(huán)境中可以正常工作,我們需要進(jìn)行數(shù)據(jù)庫集成測試,在本文中,我們將介紹如何使用 SpringBoot 中的 EmbeddedDatabaseBuilder 來進(jìn)行數(shù)據(jù)庫集成測試2023-07-07
Java使用selenium爬取b站動態(tài)的實(shí)現(xiàn)方式
本文主要介紹了Java使用selenium爬取b站動態(tài)的實(shí)現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Java 圖解Spring啟動時(shí)的后置處理器工作流程是怎樣的
spring的后置處理器有兩類,bean后置處理器,bf(BeanFactory)后置處理器。bean后置處理器作用于bean的生命周期,bf的后置處理器作用于bean工廠的生命周期2021-10-10
mybatis-plus常用注解@TableId和@TableField的用法
本文主要介紹了mybatis-plus常用注解@TableId和@TableField的用法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
springboot自定義starter實(shí)現(xiàn)過程圖解
這篇文章主要介紹了springboot自定義starter實(shí)現(xiàn)過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Spring?Boot實(shí)現(xiàn)文件上傳的兩種方式總結(jié)
應(yīng)用開發(fā)過程中,文件上傳是一個(gè)基礎(chǔ)的擴(kuò)展功能,它的目的就是讓大家共享我們上傳的文件資源,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Spring?Boot實(shí)現(xiàn)文件上傳的兩種方式,需要的朋友可以參考下2023-05-05
SpringBoot 使用jwt進(jìn)行身份驗(yàn)證的方法示例
這篇文章主要介紹了SpringBoot 使用jwt進(jìn)行身份驗(yàn)證的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12
Java實(shí)現(xiàn)導(dǎo)出Word文檔的示例代碼
poi-tl是一個(gè)基于Apache POI的Word模板引擎,也是一個(gè)免費(fèi)開源的Java類庫,你可以非常方便的加入到你的項(xiàng)目中。本文就利用它實(shí)現(xiàn)導(dǎo)出Word文檔功能,需要的可以參考一下2023-02-02
Java Swing中JDialog實(shí)現(xiàn)用戶登陸UI示例
這篇文章主要介紹了Java Swing中JDialog實(shí)現(xiàn)用戶登陸UI功能,結(jié)合完整實(shí)例形式分析了Swing使用JDialog實(shí)現(xiàn)用戶登陸UI界面窗口功能的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-11-11

