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}