欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MyBatis基礎支持DataSource實現(xiàn)源碼解析

 更新時間:2023年02月05日 11:11:28   作者:念念清晰  
這篇文章主要為大家介紹了MyBatis基礎支持DataSource實現(xiàn)源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

DataSource

在數據庫應用中,客戶端與數據庫服務端建立的連接對象(Connection)是寶貴的資源,每次請求數據庫都創(chuàng)建連接,使用完畢后會銷毀連接,這是一種很浪費資源的操作。因此Java提出了DataSource接口??梢园阉斪饕粋€連接池。程序初始化時,創(chuàng)建一批連接放入到連接池中,如果需要請求數據庫就從連接池中取出連接對象(Connection)使用完畢后把連接歸還給連接池。這樣就減少了每次請求都創(chuàng)建、銷毀連接的步驟,從而提高數據庫性能。

package javax.sql;
public interface DataSource  extends CommonDataSource, Wrapper {
  // 最重要的方法
  Connection getConnection() throws SQLException;
  // 其他方法不再列出
}

Java只是在JDK1.4版本發(fā)布了該接口規(guī)范。具體實現(xiàn)需要用戶自己實現(xiàn)。MyBatis中提供了3種DataSource接口的實現(xiàn)。

  • UnpooledDataSource
  • PooledDataSource
  • JNDI方式的接口(不在本文討論范圍)

下面著重分析1和2這兩種DataSource的實現(xiàn)。

UnpooledDataSource

UnpooledDataSource顧名思義,他是非池化的DataSource,說白了和普通的Connection沒什么區(qū)別。通過UnpooledDataSource過去連接每次都需要重新創(chuàng)建一個Connection。我們來看下它的getConnection實現(xiàn)方法。

public Connection getConnection() throws SQLException {
  return doGetConnection(username, password);
}
private Connection doGetConnection(Properties properties) throws SQLException {
  initializeDriver();
  Connection connection = DriverManager.getConnection(url, properties);
  configureConnection(connection);
  return connection;
}

在UnpooledDataSource#getConnection方法中,調用了doGetConnection方法,參數是username和password,該方法也就是通過用戶名和密碼獲取數據庫連接的意思。doGetConnection具體實現(xiàn)就使用了DriverManager來獲取連接對象。這是JDBC原生獲取連接對象的方式。

值得一說的是:UnpooledDataSource的其他方法都是基于DriverManager實現(xiàn)的。也就是說,使用UnpooledDataSource作為連接池的話等價于沒有使用連接池。

PooledDataSource

PooledDataSource才是真正意義上的連接池,它提供了連接池的大小(默認10)、最大活躍連接數量、空閑連接數量等蠶食設置。并且對Connection對象進行了JDK動態(tài)代理,重寫了Connection的close方法。使得Connection對象在調用close方法是不是真正的關閉連接,而是把自定義關閉行為,MyBatis的關閉邏輯就是把Connection對象歸還連接池。

我們先看下PooledDataSource的幾個重要字段信息

public class PooledDataSource implements DataSource {
  // PooledDataSource真正管理連接狀態(tài)的是PoolState,后面會詳細說明
  private final PoolState state = new PoolState(this);
  // UnpooledDataSource上面說過和普通的Connection無異
  private final UnpooledDataSource dataSource;
  //正在使用連接的數量
  protected int poolMaximumActiveConnections = 10;
  //空閑連接數
  protected int poolMaximumIdleConnections = 5;
  //在被強制返回之前,池中連接被檢查的時間
  protected int poolMaximumCheckoutTime = 20000;
  //這是給連接池一個打印日志狀態(tài)機會的低層次設置,還有重新 嘗試獲得連接, 這些情況下往往需要很長時間 為了避免連接池沒有配置時靜默失 敗)。
  protected int poolTimeToWait = 20000;
  //發(fā)送到數據的偵測查詢,用來驗證連接是否正常工作,并且準備 接受請求。默認是“NO PING QUERY SET” ,這會引起許多數據庫驅動連接由一 個錯誤信息而導致失敗
  protected String poolPingQuery = "NO PING QUERY SET";
  //開啟或禁用偵測查詢
  protected boolean poolPingEnabled = false;
  //用來配置 poolPingQuery 多次時間被用一次
  protected int poolPingConnectionsNotUsedFor = 0;
  private int expectedConnectionTypeCode;
}

這些字段主要記錄了連接池的重要信息:連接池大小、空閑時最大連接數、最大活躍連接數、超時時間等。而整整揭開PooledDataSource獲取連接對象的神秘面紗還需要介紹兩個類。PooledConnection和PoolState

PooledConnection

PooledConnection實現(xiàn)了InvocationHandler接口,他是用來做JDK動態(tài)代理的。前文提到過,mybatis使用JDK動態(tài)代理重寫了Connection對象的close方法,就是在該類中實現(xiàn)的邏輯。該類有幾個重要屬性。

  • private PooledDataSource dataSource; // dataSource的副本
  • private Connection realConnection; // 真實連接對象
  • private Connection proxyConnection; // 實際返回的代理對象

接下來來看下代理對象的invoke方法是如何重寫close方法的。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  String methodName = method.getName();
  //如果調用close的話,忽略它,反而將這個connection加入到池中
  if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
    dataSource.pushConnection(this);
    return null;
  } 
  return method.invoke(realConnection, args);
  // 其他邏輯省略....
}

在invoke方法中判斷下執(zhí)行的方法名稱是否是Close,如果是,就不再執(zhí)行原來的close方法了,而是執(zhí)行PooledDataSource 的pushConnection方法!從方法名可以看出方法的作用是:把連接push到連接池PooledDataSource 中。pushConnection的邏輯后文詳細說明

PoolState

上文提到PooledDataSource并不管理連接對象。那么程序初始化的時候創(chuàng)建的一批連接存放到哪里了呢?答案是存在PoolState對象中,而PooledDataSource有一個屬性就是PoolState。也就是說PooledDataSource是通過PoolState來管理連接池的。

一批連接在Java中就是一個List集合嘛。那么我們想一下PoolState都需要怎么管理連接呢?首先根據連接的狀態(tài),可以把連接分為2種

  • 空閑連接protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
  • 活躍連接protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

PoolState中兩個List屬性分別存儲空閑連接和活躍連接。需要連接的時候就從idleConnections 列表中取,關聯(lián)連接時就把連接從activeConnections 中移到idleConnections 中。

PoolState中還有一些其他的統(tǒng)計信息字段,比如 請求次數、請求的總時間、總連接數等這些屬性比較簡單就不再列出了

獲取連接

介紹完PooledConnection和PoolState這兩個類后,我們來看下PooledDataSource是怎么獲取連接的。獲取連接的邏輯在PooledDataSource#getConnection方法中,getConnection方法只是一個殼子,具體調用邏輯在popConnection方法。我們來看一下(我只列出了重要邏輯)

public Connection getConnection() throws SQLException {
  return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
private PooledConnection popConnection(String username, String password) throws SQLException {
  //最外面是while死循環(huán),如果一直拿不到connection,則不斷嘗試
  while (conn == null) {
    synchronized (state) {
      if (!state.idleConnections.isEmpty()) {
        //如果有空閑的連接的話,返回第一個空閑連接
        conn = state.idleConnections.remove(0);
      } else {
        //如果沒有空閑的連接
        if (state.activeConnections.size() &lt; poolMaximumActiveConnections) {
          //如果activeConnections太少,那就new一個PooledConnection
          conn = new PooledConnection(dataSource.getConnection(), this);
        } else {
          //如果activeConnections已經很多了,那不能再new了
          //取得activeConnections列表的第一個(最老的)
          PooledConnection oldestActiveConnection = state.activeConnections.get(0);
          long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
          if (longestCheckoutTime &gt; poolMaximumCheckoutTime) {
            //如果checkout時間過長,則這個connection標記為overdue(過期)
            //刪掉最老的連接,然后再new一個新連接
            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
            oldestActiveConnection.invalidate();
          } else {
            //如果checkout時間不夠長,沒辦法,只能等待,在此分支會記錄一些統(tǒng)計信息
          }
        }
      }
      if (conn != null) {
        if (conn.isValid()) {
          //如果已經拿到connection,則記錄一些統(tǒng)計信息
        } else {
          //如果沒拿到,統(tǒng)計信息:壞連接+1
          state.badConnectionCount++;
          localBadConnectionCount++;
          conn = null;
          //如果好幾次都拿不到,就放棄了,拋出異常
        }
      }
    }
  }
  return conn;
}

在popConnection中

  • 從PoolState對象的空閑連接列表中獲取連接,如果有空閑連接就返回。
  • 從PoolState對象的活躍連接列表中獲取連接,如果連接數小于最大活躍數,則new一個連接返回。如果沒有只能等待其他線程釋放連接再進行獲取
  • 無論是否獲取到連接,對連接進行一些信息統(tǒng)計并記錄到PoolState對象中。一旦嘗試獲取連接的時間超過了閾值,就會放棄獲取連接拋出異常

關閉連接

在PooledConnection小節(jié)中見到,PooledConnection重寫了Connection的close方法。當調用Connection的close方法時真正執(zhí)行的邏輯是PooledDataSource的pushConnection方法。該代碼邏輯很簡單,大體上說,就是把連接從活躍列表中刪除,加入到空閑列表中。具體實現(xiàn)如下

protected void pushConnection(PooledConnection conn) throws SQLException {
  synchronized (state) {
    //先從activeConnections中刪除此connection
    state.activeConnections.remove(conn);
    if (conn.isValid()) {
      if (state.idleConnections.size() &lt; poolMaximumIdleConnections &amp;&amp; conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
        //如果空閑的連接太少,
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        if (!conn.getRealConnection().getAutoCommit()) {
          conn.getRealConnection().rollback();
        }
        //new一個新的Connection,加入到idle列表
        PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
        state.idleConnections.add(newConn);
        //通知其他線程可以來搶connection了
        state.notifyAll();
      } else {
       //否則,即空閑的連接已經足夠了
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        //那就將connection關閉就可以了,獲取真正的connection對象并且關閉
        conn.getRealConnection().close();
        conn.invalidate();
      }
    } 
  }
}

關閉過程:

  • 空閑連接數<最大空閑連接數 則新建一個連接存放到PoolState的空閑列表中并通知其他線程可以來搶Connection對象
  • 如果PoolState的空閑列表是滿的,那只能獲取真正的connection對象并將其關閉了。

小結

  • PooledDataSource真正意義上實現(xiàn)了DataSource接口。具有連接池的意義
  • PooledDataSource通過PooledConnection和PoolState來管理連接池中的連接
  • PooledConnection重寫了Connection對象的close方法。調用Connection的close方法時并不會真正的關閉連接,而是先要進行歸還連接的操作。
  • PoolState是對連接列表狀態(tài)的管理。它有兩個List屬性,分別存儲了活躍連接列表空閑連接列表

DataSourceFactory

獲取MyBatis提供的DataSource實現(xiàn),需要通過工廠DataSourceFactory接口來獲取。在這里MyBatis使用了工廠方法模式。DataSourceFactory有兩個實現(xiàn)類。分別是

  • UnpooledDataSourceFactory
  • PooledDataSourceFactory

我們首先來看下工廠接口定義

public interface DataSourceFactory {
  //設置屬性,被XMLConfigBuilder所調用
  void setProperties(Properties props);
  //生產數據源,直接得到javax.sql.DataSource
  DataSource getDataSource();
}

其中最重要的方法就是getDataSource,它很直觀,通過工廠對象的該方法可以獲取DataSource實現(xiàn)。

UnpooledDataSourceFactory

UnpooledDataSourceFactory獲取dataSource的方法非常簡單直觀。

首先,構造方法里里new了一個UnpooledDataSource對象存放到工廠的屬性中

然后,getDataSource直接返回該對象即可。具體實現(xiàn)如下

public class UnpooledDataSourceFactory implements DataSourceFactory {
  protected DataSource dataSource;
  public UnpooledDataSourceFactory() {
    this.dataSource = new UnpooledDataSource();
  }
  public DataSource getDataSource() {
    return dataSource;
  }
}

PooledDataSourceFactory

PooledDataSourceFactory就有意思了,想偷懶,直接繼承自UnpooledDataSourceFactory。只需要在構造方法中new一個PooledDataSource對象,再通過getDataSource方法獲取即可。

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
  //數據源換成了PooledDataSource
  public PooledDataSourceFactory() {
    this.dataSource = new PooledDataSource();
  }
}

結語

個人感覺mybatis提供的DataSourceFactory的實現(xiàn)類有點雞肋??梢哉f還是new對象。我們知道工廠模式創(chuàng)建的一般都是比較復雜的對象,是用來幫助開發(fā)者屏蔽復雜的細節(jié)。而mybatis的這兩個實現(xiàn)都只是new對象而已。

以上就是MyBatis基礎支持DataSource實現(xiàn)源碼解析的詳細內容,更多關于MyBatis基礎支持DataSource的資料請關注腳本之家其它相關文章!

相關文章

  • 詳解Java編寫并運行spark應用程序的方法

    詳解Java編寫并運行spark應用程序的方法

    這篇文章主要介紹了詳解Java編寫并運行spark應用程序的方法,內容詳細,結合了作者實際工作中的問題進行具體分析,具有一定參考價值。
    2017-09-09
  • Java中性能優(yōu)化的35種方法匯總

    Java中性能優(yōu)化的35種方法匯總

    很多同學在日常寫Java的時候很少去關心性能問題,但是在我們寫代碼的過程中必須考慮到性能對程序的影響。小到我們使用位運算來實現(xiàn)算術運算,大到我們對 Java 代碼的總體架構設計,性能其實離我們很近。本文介紹了Java中性能優(yōu)化的35種方法,需要的朋友可以參考下。
    2017-01-01
  • Mybatis中@Param注解的用法詳解

    Mybatis中@Param注解的用法詳解

    @Param注解的作用是給參數命名,參數命名后就能根據名字得到參數值,正確的將參數傳入sql語句中,下面這篇文章主要給大家介紹了關于Mybatis中@Param注解用法的相關資料,需要的朋友可以參考下
    2022-07-07
  • ElasticSearch創(chuàng)建后索引修改數據類型方法步驟

    ElasticSearch創(chuàng)建后索引修改數據類型方法步驟

    Elasticsearch存儲數據之前需要先創(chuàng)建索引,類似于結構型數據庫建庫建表,創(chuàng)建索引時定義了每個字段的索引方式和數據類型,這篇文章主要給大家介紹了關于ElasticSearch創(chuàng)建后索引修改數據類型的方法步驟,需要的朋友可以參考下
    2023-09-09
  • Spring條件注解@Conditional示例詳解

    Spring條件注解@Conditional示例詳解

    這篇文章主要給大家介紹了關于Spring條件注解@Conditional的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Spring具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-08-08
  • Java?數據庫連接池Druid?的介紹

    Java?數據庫連接池Druid?的介紹

    這篇文章主要給大家分享的是?Java?數據庫連接池Druid?的介紹,Druid是一個JDBC組件,它包括三部分:?DruidDriver?代理Driver,能夠提供基于Filter-Chain模式的插件體系。?DruidDataSource?高效可管理的數據庫連接池,下面來看看文中的詳細內容,需要的朋友也可以參考一下
    2021-11-11
  • Java中數組的創(chuàng)建與傳參方法(學習小結)

    Java中數組的創(chuàng)建與傳參方法(學習小結)

    這篇文章主要介紹了Java中數組的創(chuàng)建與傳參方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09
  • @TableField注解之深入理解與應用方式

    @TableField注解之深入理解與應用方式

    在現(xiàn)代軟件開發(fā)中,@TableField注解作為MyBatis-Plus中的一個重要特性,用于定義實體類字段與數據庫表字段的映射關系,本文詳細介紹了@TableField注解的使用場景、屬性及其在實際開發(fā)中的應用,包括字段名稱映射、非數據庫字段標識、字段填充策略
    2024-10-10
  • springboot中redis正確的使用詳解

    springboot中redis正確的使用詳解

    本文主要介紹了springboot中redis正確的使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Java?如何通過注解實現(xiàn)接口輸出時數據脫敏

    Java?如何通過注解實現(xiàn)接口輸出時數據脫敏

    這篇文章主要介紹了Java?如何通過注解實現(xiàn)接口輸出時數據脫敏,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評論