blog

基本モジュールDataSourceのMyBatisソースコード解析

\n前のレビュー\n\nこの操作方法の欠点を解決するために、バインディングモジュールのmybatisバージョンで。これは、コンパイル期間中に問題を見つけることができるようにコンパイルすることができます...

Aug 27, 2020 · 24 min. read
シェア

基本モジュールDataSourceのMyBatisソースコード解析

序文

前節では、Mapperインターフェースバインディングのソースロジックを学びました。今回は、MyBatis の DataSource データソースモジュールを学びます。

この操作方法の欠点を解決するために、バインディングモジュールのmybatisバージョンで提供されています。これは、問題を見つけることができるコンパイル期間にコンパイルすることができます。同時にjdkの動的プロキシモデルの使用を通じて、開発者は、永続化層の開発を完了するために対応するインターフェイスを記述する必要があります。つまり、作業負荷を減らすために、エラーの確率の大きな削減があります。

次に、バインディングの実行ロジックをソースコードを通して詳しく紹介します。

背景知識

一般的なデータソースは、javax.sql.Datasourceの実装に基づいてされるため。 Mybatisのデータソースの実装もjavax.sql.Datasourceの実装に基づいて設計するだけでなく、MyBatisデータソースの実装の導入では、我々は最初のJDKのDataSourceを理解しています。

オラクル公式サイトのDataSourceのjdkについてDataSource、以下のような記述があります:

A factory for connections to the physical data source that this DataSource object represents. An alternative to the DriverManager facility, a DataSource object is the preferred means of getting a connection. An object that implements the DataSource interface will typically be registered with a naming service based on the JavaNaming and Directory API.

The DataSource interface is implemented by a driver vendor. There are three types of implementations:

  1. Basic implementation -- produces a standard Connection object
  2. Connection pooling implementation -- produces a Connection object that will automatically participate in connection pooling. This implementation works with a middle-tier connection pooling manager.
  3. Distributed transaction implementation -- produces a Connection object that may be used for distributed transactions and almost always participates in connection pooling. This implementation works with a middle-tier transaction manager and almost always with a connection pooling manager.

A DataSource object has properties that can be modified when necessary. For example, if the data source is moved to a different server, the property for the server can be changed. The benefit is that because the data source's properties can be changed, any code accessing that data source does not need to be changed.

A driver that is accessed via a DataSource object does not register itself with the DriverManager. Rather, a DataSource object is retrieved though a lookup operation and then used to create a Connection object. With a basic implementation, the connection obtained through a DataSource object is identical to a connection obtained through the DriverManager facility.

An implementation of DataSource must include a public no-arg constructor.

この DataSource オブジェクトで表される物理データソースに接続するためのファクトリ。DriverManager ツールの代わりに、DataSource オブジェクトを使用して接続を取得する方法が推奨されます。DataSource インターフェイスを実装するオブジェクトは通常、JavaNaming and Directory API に基づくネーミング・サービスを実装するオブジェクトに登録されます。

DataSource インターフェースは、ドライバ・ベンダによって実装されます。実装には3つのタイプがあります:

基本実装 - 標準接続オブジェクトを生成します。 接続プール実装 - 接続プールに自動的に参加する接続オブジェクトを生成します。この実装は、中間層の接続プール・マネージャと共に使用されます。分散トランザクション実装 - 分散トランザクションに使用でき、ほぼ常に接続プールに参加する接続オブジェクトを生成します。この実装は、中間層のトランザクション・マネージャと共に使用され、ほとんどの場合、接続プール・マネージャと共に使用されます。DataSource オブジェクトには、必要に応じて変更できるプロパティがあります。例えば、DataSource を別のサーバーに移動した場合、そのサーバーのプロパティを変更することができます。データソースのプロパティを変更できるため、そのデータソースにアクセスするコードを変更する必要がないという利点があります。

DataSource オブジェクトを介してアクセスされたドライバは、DriverManager に登録されません。その代わりに、DriverManager はルックアップ操作によって DataSource オブジェクトを取得し、それを使用して Connection オブジェクトを作成します。基本的な実装では、DataSource オブジェクトを通じて取得される接続は、DriverManager ツールを通じて取得される接続と同じです。

DataSourceの実装には、publicなパラメータなしのコンストラクタを含める必要があります。

DataSourceの実装によっては、パブリックパラメータレスコンストラクタの記述が必要です。

これが、PooledDataSourceとUnpooledDataSourceの両方が、ソース・コードを分析すると以下のように、明示的にパラメータなしのコンストラクタを定義している理由です。

ここではDataSourceについて簡単に紹介していますので、興味のある方は関連情報やjdkのソースコードなどを確認してみてください。

アーキテクチャ設計

org.apache.ibatis.datasourceDataSourceモジュールは、以下のように分割されたパッケージパスにあります:

datasource
- jndi
 - JndiDataSourceFactory
- pooled
 - PooledConnection
 - PooledDataSource
 - PooledDataSourceFactory
 - PoolState
- unpooled
 - UnpooledDataSource
 - UnpooledDataSourceFactory
- DataSourceException
- DataSourceFactory

対応するクラス・アーキテクチャ設計図は以下のとおりです:

アーキテクチャ図から、アーキテクチャが古典的なファクトリーメソッド設計パターンを使用していることがわかります。

ソースコードの解釈

DataSourceFactory

DataSourceFactory インターフェイスには、データ・ソースの関連プロパティを設定する setProperties() メソッドと、データ・ソースを取得する getDataSource() メソッドの 2 つしかありません。

/**
 * DataSourceファクトリーインターフェースのクラスは、2つのメソッドしか提供しない:
 * 1.関連する設定プロパティを設定する
 * 2.データソースを取得する
 * 3.このインターフェースには3つの実装クラスがある:PooledDataSourceFactory,UnpooledDataSourceFactory,JndiDataSourceFactory
 */
public interface DataSourceFactory {
 //プロパティを設定する
 void setProperties(Properties props);
 //データソースオブジェクトを取得する
 DataSource getDataSource();
}

JndiDataSourceFactoryUnpooledDataSourceFactoryPooledDataSourceFactoryMyBatisは3つのDataSourceFactory実装を提供します。これらを一つずつ紹介します。

UnpooledDataSourceFactory

UnpooledDataSourceFactoryDataSourceFactory 内の実装は、まず、そのコンストラクタ・メソッドで UnpooledDataSource を直接インスタンス化し、getDataSource() メソッドを介して UnpooledDataSource を取得します。 UnpooledDataSource のプロパティは、getDataSource() メソッドを介して設定されます。メソッドによって設定されます。詳細は、以下のソース・コードを参照してください:

package org.apache.ibatis.datasource.unpooled;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
public class UnpooledDataSourceFactory implements DataSourceFactory {
 private static final String DRIVER_PROPERTY_PREFIX = "driver.";
 private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
 protected DataSource dataSource;
 public UnpooledDataSourceFactory() {
 this.dataSource = new UnpooledDataSource();
 }
 /**
 * プロパティから設定情報を取得する
 */
 @Override
 public void setProperties(Properties properties) {
 Properties driverProperties = new Properties();
 MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
 for (Object key : properties.keySet()) {
 String propertyName = (String) key;
 if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
 //"driver."DataSource関連のコンフィギュレーションの最初に、driverPropertiesオブジェクトに保存される。
 String value = properties.getProperty(propertyName);
 driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
 } else if (metaDataSource.hasSetter(propertyName)) {
 //通常の設定プロパティは、metaDataSourceに直接設定する。
 String value = (String) properties.get(propertyName);
 //プライベート型変換メソッドを呼び出す
 Object convertedValue = convertValue(metaDataSource, propertyName, value);
 metaDataSource.setValue(propertyName, convertedValue);
 } else {
 throw new DataSourceException("Unknown DataSource property: " + propertyName);
 }
 }
 /** driverPropertiesオブジェクトに値を設定する*/
 if (driverProperties.size() > 0) {
 metaDataSource.setValue("driverProperties", driverProperties);
 }
 }
 @Override
 public DataSource getDataSource() {
 return dataSource;
 }
 /** プロパティタイプのプロパティ名に対応する値の値に変換するために、主な処理によると 整数、長、ブーリアン3種類の*/
 private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
 Object convertedValue = value;
 Class<?> targetType = metaDataSource.getSetterType(propertyName);
 if (targetType == Integer.class || targetType == int.class) {
 convertedValue = Integer.valueOf(value);
 } else if (targetType == Long.class || targetType == long.class) {
 convertedValue = Long.valueOf(value);
 } else if (targetType == Boolean.class || targetType == boolean.class) {
 convertedValue = Boolean.valueOf(value);
 }
 return convertedValue;
 }
}

PooledDataSourceFactory

PooledDataSourceFactoryUnpooledDataSourceFactory実装ははるかに簡単ですが、彼はsetProperties()、getDataSource()メソッドを書き換えませんでしたが、直接継承し、唯一の違いは、コンストラクタのdataSourceインスタンス化PooledDataSourceのオブジェクトは、具体的なコードは次のとおりです。

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
 public PooledDataSourceFactory() {
 //UnpooledDataSourceFactoryとの唯一の違い
 this.dataSource = new PooledDataSource();
 }
}

JndiDataSourceFactory

JndiDataSourceFactoryJavaアプリケーションは、これらのネーミングサービスやディレクトリサービスと対話できるように、JNDI APIは、特定のネーミングサービスやディレクトリシステムにマッピングされるの管理者によって、JNDIサービスプロビジョニングインターフェイスは、統一されたクライアント側のAPIを提供します。ネーミングサービスやディレクトリサービス。基本的な目的は、結合を減らし、展開の柔軟性を提供し、保守コストを削減することです。

JndiDataSourceFactoryDataSourceの設定は特定のデータソースベンダーに依存する必要があるため、コンストラクタでインスタンス化することはできません。そのため、コンストラクタでインスタンス化することはできません。そのため、設定から関連する情報を読み取り、Contextコンテキストに従って、ルックアップ方式でインスタンス化します。

package org.apache.ibatis.datasource.jndi;
import java.util.Map.Entry;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
/**
 * Jndiデータソースファクトリークラス
 */
public class JndiDataSourceFactory implements DataSourceFactory {
 public static final String INITIAL_CONTEXT = "initial_context";
 public static final String DATA_SOURCE = "data_source";
 public static final String ENV_PREFIX = "env.";
 private DataSource dataSource;
 /**
 * 渡されたプロパティに基づいて、"env "で始まるデータソースのコードを取得する。."key-valueペアの属性をenvオブジェクトに格納する。
 * envがnullの場合、InitialContextコンストラクタを参照なしで呼び出す。
 * envがNULLでない場合、InitialContextパラメトリックコンストラクタの呼び出しに従って、envオブジェクトが渡され、初期化される。
 *
 * 入力されるプロパティに初期値がある場合_context属性とデータの存在_source属性は、最初の_contextinitCtxを介してキーとして見つける。
 * 次に、ctxオブジェクトを見つけることによると、データに続ける_sourceキーを検索し、返されたオブジェクトをDataSourceデータソースに変換する
 * そうでなければ、もしプロパティに_source MyBatis DataSourceモジュールのキーがMyBatis DataSourceモジュールのキーと同じであれば、initCtxを直接呼び出してオブジェクトを取得し、DataSourceデータソースを返すことができる。
 * */
 @Override
 public void setProperties(Properties properties) {
 try {
 InitialContext initCtx;
 Properties env = getEnvProperties(properties);
 if (env == null) {
 initCtx = new InitialContext();
 } else {
 initCtx = new InitialContext(env);
 }
 if (properties.containsKey(INITIAL_CONTEXT)
 && properties.containsKey(DATA_SOURCE)) {
 Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT));
 dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE));
 } else if (properties.containsKey(DATA_SOURCE)) {
 dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));
 }
 } catch (NamingException e) {
 throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
 }
 }
 @Override
 public DataSource getDataSource() {
 return dataSource;
 }
 /** プロパティの情報によると、"env "を取得する。."モジュール冒頭の設定情報を削除した(envを削除)。."キーと値のペアがcontextPropertiesに追加される。*/
 private static Properties getEnvProperties(Properties allProps) {
 final String PREFIX = ENV_PREFIX;
 Properties contextProperties = null;
 for (Entry<Object, Object> entry : allProps.entrySet()) {
 String key = (String) entry.getKey();
 String value = (String) entry.getValue();
 if (key.startsWith(PREFIX)) {
 if (contextProperties == null) {
 contextProperties = new Properties();
 }
 contextProperties.put(key.substring(PREFIX.length()), value);
 }
 }
 return contextProperties;
 }
}

UnpooledDataSource

最初に、UnpooledDataSource クラスのプロパティをいくつか見てください:

 private ClassLoader driverClassLoader; //driverMyBatisクラスローダー
 private Properties driverProperties; //データベース関連ドライバ設定情報
 private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>(); //DriverManager登録ドライバからコピーする!
 private String driver; //ドライバクラスのパス名
 private String url; //データベース接続アドレス
 private String username; //データベースユーザー名
 private String password; //データベースパスワード
 private Boolean autoCommit; //自動投稿するかどうか
 private Integer defaultTransactionIsolationLevel; //トランザクションの分離レベル
 private Integer defaultNetworkTimeout; //デフォルトのネットワーク・タイムアウト

registeredDrivers の場合、このプロパティの設定は、DriverManager に登録されているドライバーからコピーを作成する静的コードブロックによって初期化されます。コードは以下のとおりです:

/** 登録されたドライバーを初期化する*/
static {
 Enumeration<Driver> drivers = DriverManager.getDrivers();
 while (drivers.hasMoreElements()) {
 Driver driver = drivers.nextElement();
 registeredDrivers.put(driver.getClass().getName(), driver);
 }
}

javax.sql.Datasourceそして、UnpooledDataSourceのためのいくつかのコンストラクタのオーバーロード、インターフェースのメソッド実装、上記のプロパティのためのゲッター/セッターメソッドがあります。内容は比較的単純であり、再度説明することはありません。

UnpooledDataSource#doGetConnection(java.lang.String, java.lang.String)UnpooledDataSource#getConnection()ぜなら、すべてのオーバーロードされたメソッドが最終的に呼び出されるメソッドだからです。doGetConnection(java.lang.String, java.lang.String) org.apache.ibatis.datasource.unpooled.UnpooledDataSource#doGetConnection(java.util.Properties)このメソッドの主なロジックは、ユーザー名、パスワード、registeredDrivers をプロパティにまとめ、次のコードでオーバーロードされたメソッドを呼び出すことです:

private Connection doGetConnection(String username, String password) throws SQLException {
 Properties props = new Properties();
 if (driverProperties != null) {
 props.putAll(driverProperties);
 }
 if (username != null) {
 props.setProperty("user", username);
 }
 if (password != null) {
 props.setProperty("password", password);
 }
 return doGetConnection(props);
}

データベース接続は、doGetConnection オーバーロード・メソッドによって以下のように取得されます:

private Connection doGetConnection(Properties properties) throws SQLException {
 //ドライバを初期化する
 initializeDriver();
 //接続を取得するデータベースのアドレスと関連するプロパティによると
 Connection connection = DriverManager.getConnection(url, properties);
 //ネットワーク・タイムアウト、自動コミット、トランザクション分離レベルなど、その他の接続プロパティを設定する。
 configureConnection(connection);
 //接続オブジェクトを返す
 return connection;
 }
 /**
 * 初期化ドライバ
 * まず、UnpooledDataSource定義のdriver属性にregisteredDriversが既に存在するかどうかを判断する。
 * もし
 * 2.driverClassLoaderが空でなければ、ドライバークラスローダーからドライバータイプを取得する。.classForName 
 * 2.ドライバクラスの種類によると、対応するドライバのインスタンスを作成し、プロキシオブジェクトのドライバのインスタンスは、DriverManagerとregisteredDriversに追加される
 * そうでなければ、ドライバ・クラスの初期化例外がスローされる。
 */
 private synchronized void initializeDriver() throws SQLException {
 System.out.println("**************** driver +driver);
 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://..com/~nsayer/Java/dyn-.html
 Driver driverInstance = (Driver)driverType.getDeclaredConstructor().newInstance();
 DriverManager.registerDriver(new DriverProxy(driverInstance));
 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 (defaultNetworkTimeout != null) {
 conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
 }
 if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
 conn.setAutoCommit(autoCommit);
 }
 if (defaultTransactionIsolationLevel != null) {
 conn.setTransactionIsolation(defaultTransactionIsolationLevel);
 }
 }

これで、UnpooledDataSourceのコアの紹介は終了です。

しかし、実際の開発現場では、プールされていないデータソースは使用されません。JDBCプログラミングに慣れたパートナーであれば、データベース接続の作成プロセスは比較的時間がかかり、実際のデータベース実行よりもデータベース接続の作成に時間がかかる可能性があることを知っているはずだと思います。また、データベースのリンク番号リソースはメモリのようなもので、貴重であると同時に限られています。このようなリソースの無駄を減らすために、賢いプログラマは解決策としてコネクションプーリングを考え出しました。これにより、時間のかかるコネクションプールの作成が不要になり、コネクションの再利用も可能になります。これは理想的ですが、現実にはそうではありません。このプログラムは、接続を作成する問題を解決しますが、最終的にどのように多くの接続が適切である、多くのアプリケーションの練習の後、mybatisは、問題を解決するための方法の動的構成を介してビジネスの量のアプリケーションに応じて、より良いソリューションを提供します。以下は、PooledDataSourceの内容について学び続けます。

PooledDataSource を紹介するために、上のアーキテクチャー図を見てください。javax.sql.Connectionアーキテクチャ図からわかるように、PooledDataSource は直接メンテナンスを行うのではなく、PooledConnection を介して間接的に管理されます。

PooledConnection

PooledConnectionクラスはInvocationHandlerインタフェースを実装しており、JDKのダイナミック・プロキシ・モデルも使用しているようです。このクラスには、関連するプロパティ、コンストラクタ・メソッド、およびインボーカ・メソッドの実装という3つの核となるポイントがあります。具体的な説明とビジネスロジックは以下の通り:

class PooledConnection implements InvocationHandler {
 private static final String CLOSE = "close"; //接続オブジェクトを閉じるメソッドの呼び出しがcloseかどうか。
 private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
 private final int hashCode; //接続のハッシュコード
 private final PooledDataSource dataSource; //接続オブジェクトのプールされたデータソース
 private final Connection realConnection; //本当の接続オブジェクト
 private final Connection proxyConnection; //プロキシ接続オブジェクト
 private long checkoutTimestamp; //接続オブジェクトのタイムスタンプをチェックアウトする
 private long createdTimestamp; //接続オブジェクトの作成タイムスタンプ
 private long lastUsedTimestamp; //接続オブジェクトの最後の使用のタイムスタンプ
 private int connectionTypeCode; //データベースのURL、ユーザー名、パスワードに基づいて接続タイプのコードを生成する。
 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);
 }
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 String methodName = method.getName();
 // メソッドが閉じているかどうかを判断し、はい、フリーリストに接続を返すためにpushConnectionメソッドを呼び出すだろう
 if (CLOSE.equals(methodName)) {
 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
 // 接続オブジェクトが有効かどうかをチェックする
 checkConnection();
 }
 // 本当の接続を呼び出して返す
 return method.invoke(realConnection, args);
 } catch (Throwable t) {
 throw ExceptionUtil.unwrapThrowable(t);
 }
 }
}

PoolState

PooledDataSourceクラスには、PoolStateという別のコンポーネントがあり、このオブジェクトは主に、アイドル・リスト、アクティブな接続、タイムアウト時間など、接続オブジェクトに関連する状態情報を管理します:

public class PoolState {
 protected PooledDataSource dataSource; //接続プールのデータソース
 protected final List<PooledConnection> idleConnections = new ArrayList<>(); //アイドル接続オブジェクトリスト
 protected final List<PooledConnection> activeConnections = new ArrayList<>(); //使用中の接続オブジェクトのリストをアクティブにする
 protected long requestCount = 0; // 
 protected long accumulatedRequestTime = 0; //累積接続時間
 protected long accumulatedCheckoutTime = 0; //累積チェックアウト時間
 protected long claimedOverdueConnectionCount = 0; //タイムアウトした接続数
 protected long accumulatedCheckoutTimeOfOverdueConnections = 0; //累積タイムアウト
 protected long accumulatedWaitTime = 0; //累積待ち時間
 protected long hadToWaitCount = 0; //待機時間
 protected long badConnectionCount = 0; //無効な接続
 public PoolState(PooledDataSource dataSource) {
 this.dataSource = dataSource;
 }
}

PoolState のプロパティの値は、定数呼び出し、リコール、およびテスト中に更新されます。

PooledDataSource

最後に、PooledDataSourceについてです。これは、DataSourceの章の中で最も重要なものです。PooledDataSourceには多くのコードがあるので、いくつかの要点に集中してください。

PooledDataSource関連のプロパティ・オブジェクトです:

 private final PoolState state = new PoolState(this); //データソースの状態維持
 private final UnpooledDataSource dataSource; //コンストラクタ・メソッドでインスタンス化されたUnpooledDataSourceを通して実際の接続を得る
 // OPTIONAL CONFIGURATION FIELDS
 protected int poolMaximumActiveConnections = 10;//マックス・アクティブ
 protected int poolMaximumIdleConnections = 5; //無料接続の最大数
 protected int poolMaximumCheckoutTime = 20000; //最大チェックアウト時間
 protected int poolTimeToWait = 20000; //接続ブロックの待ち時間
 protected int poolMaximumLocalBadConnectionTolerance = 3; //接続プールで許容できる無効な接続の最大数。
 protected String poolPingQuery = "NO PING QUERY SET"; //ping  
 protected boolean poolPingEnabled; //SQL文のチェック送信を許可するかどうか
 protected int poolPingConnectionsNotUsedFor; //接続タイムアウトのしきい値は、しきい値を超える接続テストSQLを送信し、接続が使用可能かどうかをチェックする!
 private int expectedConnectionTypeCode; //期待されるデータソース接続コード: url+username+password ハッシュ計算

forceCloseAll()、このメソッドは、すべてのアイドル接続オブジェクトを閉じ、接続オブジェクトをアクティブにします。このメソッドは、ドライバ、url、ユーザ名、パスワード、autoCommint などのデータ・ソース・プロパティを設定するときに呼び出されます。

/**
* アクティブな接続のリストとアイドルな接続のリストを再帰的にループする。
* 対応するリストから接続オブジェクトを削除し、接続オブジェクトを無効に設定する。同時に、実際の接続が自動コミットに設定されているかどうかを判断し、設定されていない場合はロールバックする。
*/
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.");
 }
}

PooledDataSource#getConnection()は、新しい接続が作成されるたびに接続オブジェクトを取得するのではなく、空きリストに空いている接続があるかどうかを確認し、あれば直接取得します。具体的には、接続オブジェクトは popConnection メソッドを呼び出して取得します。ここのロジックはより複雑なので、まずロジックダイアグラムを見て、それからソースコードを解析してください。

上の図は接続オブジェクトを取得する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 { // Pool does not have available connection if (state.activeConnections.size() < poolMaximumActiveConnections) { // Can create new connection 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; 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"); } } //最も古い接続オブジェクトの情報をリセットし、関連するプロパティを設定する。 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); 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(); //poolTimeToWaitでグループを待機させる。 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 // pingで接続の有効性を確認し、有効であれば、接続オブジェクトをアクティブリストに追加し、接続要求の数をアクティブリストに追加する。+1,総接続要求時間など 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."); } //無効な接続+1 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."); } } } } }

接続オブジェクトの取得を紹介した後、PooledDataSource がどのように接続を再利用するかを見てみましょう。ビジネス・ロジック図も見てみましょう:

具体的なロジックとソースコードの解析については、コードを参照してください:

protected void pushConnection(PooledConnection conn) throws SQLException {
 synchronized (state) {
 // アクティブリストから削除する
 state.activeConnections.remove(conn);
 // 接続の有効性を判断する
 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++;
 }
 }
}

popConnectionでは、pushConnectionはconn.isValid()を呼び出して接続が有効かどうかを判断します:

public boolean isValid() {
 //validがtrueで、実際の接続がnullでなく、dataSourceが条件を通してpingできる場合のみtrueになる。
 return valid && realConnection != null && dataSource.pingConnection(this);
}
//接続の可用性を確認するためにpingを試みる。
protected boolean pingConnection(PooledConnection conn) {
 boolean result = true;
 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) {
 if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
 try {
 if (log.isDebugEnabled()) {
 log.debug("Testing connection " + conn.getRealHashCode() + " ...");
 }
 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;
 }

この時点で、DataSourceモジュールに関する核心的なポイントを紹介しました。

まとめ

DataSourceモジュールはまた、ファクトリメソッドの使用です 、 JDKの動的エージェントおよびその他のデザインパターン 。

MyBatisデータソースモジュールの紹介はここまでです。お読みいただきありがとうございました。何か間違っているところがありましたら、ご教示ください。

小宇宙の世界、ダコタの生活。

誠実なコーダーであること!

Read next

パートVII|FlinkテーブルAPIとSQLプログラミングガイド time属性の(3)

時間プロパティの紹介\n処理時間\nイベント時間\n時間プロパティの紹介\nFlink TableAPI と SQL における時間ベースの操作には、時間セマンティクスの指定が必要です。\n時間属性は、テーブル・スキーマの一部です。

Aug 27, 2020 · 6 min read