Druid接続プールのソース解析

1. Druidの使用

Druidは、Alibabaが開発した高性能なデータソース接続プールです。

1.1. Spring BootプロジェクトへのDruid統合

1.1.1. Maven依存関係の設定

<!-- Druid依存関係 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.15</version>
</dependency>

1.1.2. データソースの関連設定

spring:
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDataTimeBehavior=convertToNull&useSSL=false
      username: root
      password: root
      type: com.alibaba.druid.pool.DruidDataSource
      initialSize: 5
      maxInactive: 10
      minIdle: 5
      timeBetweenEvictionRunsMillis: 5000
      minEvictableIdleTimeMillis: 10000
      filters: stat,wall
      testOnBorrow: false

1.1.3. DruidConfig設定ファイルの定義

package com.lucky.test.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource createDataSource() {
        return new DruidDataSource();
    }
}

DruidDataSourceの接続プールを初期化し、アプリケーションが使用するデータソースとして設定します。

設定項目 説明
url
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDataTimeBehavior=convertToNull&useSSL=false
データベースへの接続URL
username root データベース接続のユーザー名
password root データベース接続のパスワード
initialSize 5 接続プールの初期接続数
minIdle 5 接続プールの最低空閑接続数
maxActive 20 接続プールの最大アクティブ接続数
maxWait 60000 接続取得のタイムアウト待ち時間(ミリ秒)
timeBetweenEvictionRunsMillis 5000 接続のIdleチェック間隔(ミリ秒)
minEvictableIdleTimeMillis 10000 Idle接続を廃棄するための最小Idle時間(ミリ秒)

2. Druid接続プールのソース解析

2.1. 接続プールの初期化

DruidDataSourceの初期化方法は以下です。

public void init() throws SQLException {
    if (initialized.get()) {
        return;
    }
    
    ReentrantLock lock = this.lock;
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        throw new SQLException("interrupted", e);
    }
    
    try {
        // 接続IDの生成
        this.id = DruidDriver.createDataSourceId();
        
        // 初期接続の作成
        int initialSize = getInitialSize();
        for (int i = 0; i < initialSize; i++) {
            Connection conn = createConnection();
            ConnectionHolder holder = new ConnectionHolder(this, conn);
            connections[poolingCount] = holder;
            poolingCount++;
        }
        
        // 接続管理スレッドの起動
        startConnectionCreateThread();
        startConnectionDestroyThread();
        initializationLatch.await();
    } finally {
        initialized.set(true);
        lock.unlock();
    }
}

初期化の主な手順は以下のとおりです。

  1. 初期化済みかどうかをチェック
  2. ロックをかけて並列初期化を防止
  3. 初期接続数を設定
  4. 接続管理スレッドを起動

2.2. 接続の取得

public PooledConnection getConnection(long maxWaitMillis) throws SQLException {
    if (disabled.get()) {
        throw new SQLException("データソースが無効です");
    }
    
    ConnectionHolder holder = null;
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        throw new SQLException("interrupted", e);
    }
    
    try {
        // 接続プールの空き接続をチェック
        if (poolingCount == 0) {
            // 空き接続がない場合、接続作成を待機
            if (maxWaitMillis <= 0) {
                throw new SQLException("接続プールがいっぱいです");
            }
            empty.await(maxWaitMillis, TimeUnit.MILLISECONDS);
        }
        
        // 空き接続の取得
        holder = connections[--poolingCount];
        connections[poolingCount] = null;
        
        // 接続の有効性チェック
        if (testOnBorrow.get()) {
            if (!testConnection(holder.getConnection())) {
                return null;
            }
        }
        
        // 接続の貸し出し
        PooledConnection pooledConn = new PooledConnection(holder);
        activeCount++;
        return pooledConn;
    } finally {
        lock.unlock();
    }
}

2.3. 接続の返却

public void returnConnection(PooledConnection pooledConn) throws SQLException {
    ConnectionHolder holder = pooledConn.getHolder();
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        throw new SQLException("interrupted", e);
    }
    
    try {
        // 接続の返却処理
        if (holder.isDiscarded()) {
            return;
        }
        
        // 接続プールへの返却
        if (poolingCount < maxActive.get()) {
            connections[poolingCount++] = holder;
            notEmpty.signal();
        } else {
            // 最大接続数に達している場合は接続を廃棄
            discardConnection(holder.getConnection());
        }
    } finally {
        lock.unlock();
    }
}

3. Druidの実装詳細

3.1. 核心クラス

  • DruidDataSource: データソースの実装クラス、getConnection()メソッドを提供
  • PooledConnection: データベース接続を表すクラス
  • ConnectionHolder: 接続プール内で管理される接続のラッパー

3.2. ReentrantLockとConditionの使用

Druidは、ReentrantLockとConditionを用いて接続の取得と返却を同期管理しています。

  • empty: 接続プールが空の場合、接続作成スレッドを通知するためのCondition
  • notEmpty: 接続プールに空き接続がある場合、接続取得スレッドを通知するためのCondition

3.3. Druidの動作フロー

getConnection()メソッドの動作フロー:

  1. ロックを取得
  2. 接続プールの状態をチェック
  3. 接続を取得
  4. ロックを解放

接続作成スレッドの動作フロー:

  1. ロックを取得
  2. 接続プールの状態をチェック
  3. 接続を作成
  4. ロックを解放

タグ: Druid接続プール JDBC ReentrantLock Condition

7月19日 21:21 投稿