001package com.streamconverter.command.rule;
002
003import com.zaxxer.hikari.HikariConfig;
004import com.zaxxer.hikari.HikariDataSource;
005import java.sql.Connection;
006import java.sql.SQLException;
007import java.time.Duration;
008import java.util.Objects;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * HikariCP接続プール設定クラス
014 *
015 * <p>HikariCPを使用したデータベース接続プールの実装です。 DatabaseConnectionPoolの置き換えとして高性能で信頼性の高い接続プール管理を提供します。
016 *
017 * <p>機能:
018 *
019 * <ul>
020 *   <li>業界標準のHikariCP接続プール
021 *   <li>自動的な接続リーク検出
022 *   <li>接続プールメトリクス
023 *   <li>設定可能なプールサイズと接続タイムアウト
024 *   <li>接続の検証と回復
025 * </ul>
026 *
027 * <p>使用例:
028 *
029 * <pre>{@code
030 * // HikariCP接続プールの作成
031 * HikariConnectionPoolConfig pool = new HikariConnectionPoolConfig("jdbc:h2:mem:testdb");
032 *
033 * // 接続の取得
034 * try (Connection connection = pool.getConnection()) {
035 *     // データベース操作
036 * }
037 *
038 * // プールのシャットダウン
039 * pool.close();
040 * }</pre>
041 */
042public class HikariConnectionPoolConfig implements AutoCloseable {
043  private static final Logger logger = LoggerFactory.getLogger(HikariConnectionPoolConfig.class);
044
045  private final HikariDataSource dataSource;
046  private final String databaseUrl;
047
048  /**
049   * デフォルト設定で接続プールを初期化
050   *
051   * @param databaseUrl データベースURL
052   * @throws IllegalArgumentException URLが無効な場合
053   */
054  public HikariConnectionPoolConfig(String databaseUrl) {
055    this(databaseUrl, 10, Duration.ofSeconds(30));
056  }
057
058  /**
059   * カスタム設定で接続プールを初期化
060   *
061   * @param databaseUrl データベースURL
062   * @param maximumPoolSize 最大プールサイズ
063   * @param connectionTimeout 接続タイムアウト
064   * @throws IllegalArgumentException パラメータが無効な場合
065   */
066  public HikariConnectionPoolConfig(
067      String databaseUrl, int maximumPoolSize, Duration connectionTimeout) {
068    Objects.requireNonNull(databaseUrl, "Database URL cannot be null");
069    if (maximumPoolSize <= 0) {
070      throw new IllegalArgumentException("Maximum pool size must be positive");
071    }
072    Objects.requireNonNull(connectionTimeout, "Connection timeout cannot be null");
073
074    this.databaseUrl = databaseUrl;
075
076    HikariConfig config = new HikariConfig();
077    config.setJdbcUrl(databaseUrl);
078    config.setMaximumPoolSize(maximumPoolSize);
079    config.setMinimumIdle(Math.min(5, maximumPoolSize / 2)); // 最小接続数は最大値の半分または5
080    config.setConnectionTimeout(connectionTimeout.toMillis());
081    config.setIdleTimeout(Duration.ofMinutes(10).toMillis()); // アイドル接続のタイムアウト
082    config.setMaxLifetime(Duration.ofMinutes(30).toMillis()); // 接続の最大生存時間
083    config.setLeakDetectionThreshold(Duration.ofMinutes(2).toMillis()); // 接続リーク検出
084
085    // 接続プールの名前を設定(デバッグ用)
086    config.setPoolName("StreamConverter-Pool");
087
088    // 接続検証クエリ(H2データベース用)
089    if (databaseUrl.contains(":h2:")) {
090      config.setConnectionTestQuery("SELECT 1");
091    }
092
093    // プロパティの設定
094    config.addDataSourceProperty("cachePrepStmts", "true");
095    config.addDataSourceProperty("prepStmtCacheSize", "250");
096    config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
097
098    this.dataSource = new HikariDataSource(config);
099
100    logger.info(
101        "HikariCP connection pool initialized - URL: {}, MaxPoolSize: {}, ConnectionTimeout: {}ms",
102        databaseUrl,
103        maximumPoolSize,
104        connectionTimeout.toMillis());
105  }
106
107  /**
108   * プールから接続を取得
109   *
110   * @return データベース接続
111   * @throws SQLException 接続取得に失敗した場合
112   */
113  public Connection getConnection() throws SQLException {
114    try {
115      Connection connection = dataSource.getConnection();
116      logger.debug("Connection obtained from HikariCP pool");
117      return connection;
118    } catch (SQLException e) {
119      logger.error("Failed to get connection from HikariCP pool: {}", e.getMessage());
120      throw e;
121    }
122  }
123
124  /**
125   * プールの現在の統計情報を取得
126   *
127   * @return 統計情報文字列
128   */
129  public String getPoolStats() {
130    if (dataSource.isClosed()) {
131      return "HikariCP[CLOSED]";
132    }
133
134    return String.format(
135        "HikariCP[active=%d, idle=%d, total=%d, waiting=%d]",
136        dataSource.getHikariPoolMXBean().getActiveConnections(),
137        dataSource.getHikariPoolMXBean().getIdleConnections(),
138        dataSource.getHikariPoolMXBean().getTotalConnections(),
139        dataSource.getHikariPoolMXBean().getThreadsAwaitingConnection());
140  }
141
142  /**
143   * プールの詳細統計情報を取得
144   *
145   * @return 詳細統計情報文字列
146   */
147  public String getDetailedStats() {
148    if (dataSource.isClosed()) {
149      return "HikariCP[CLOSED]";
150    }
151
152    com.zaxxer.hikari.HikariPoolMXBean mxBean = dataSource.getHikariPoolMXBean();
153    return String.format(
154        "HikariCP Details[active=%d, idle=%d, total=%d, waiting=%d, url=%s]",
155        mxBean.getActiveConnections(),
156        mxBean.getIdleConnections(),
157        mxBean.getTotalConnections(),
158        mxBean.getThreadsAwaitingConnection(),
159        databaseUrl);
160  }
161
162  /**
163   * プールがクローズ済みかどうかを確認
164   *
165   * @return クローズ済みの場合true
166   */
167  public boolean isClosed() {
168    return dataSource.isClosed();
169  }
170
171  /** プールをシャットダウンし、全ての接続をクローズ */
172  @Override
173  public void close() {
174    if (!dataSource.isClosed()) {
175      logger.info("Shutting down HikariCP connection pool");
176      dataSource.close();
177      logger.info("HikariCP connection pool shutdown completed");
178    }
179  }
180
181  /**
182   * DatabaseConnectionPoolとの互換性のためのメソッド
183   *
184   * @deprecated {@link #close()} を使用してください
185   */
186  @Deprecated(since = "1.2", forRemoval = true)
187  public void shutdown() {
188    close();
189  }
190}