Javaでのコネクションプーリングの簡単なガイド
1概要
接続プールはよく知られたデータアクセスパターンであり、その主な目的はデータベース接続の実行とデータベース操作の読み取り/書き込みに伴うオーバーヘッドを減らすことです。
一言で言えば、
接続プールは最も基本的なレベルではデータベース接続キャッシュの実装
であり、これは特定の要件に合うように設定できます。
このチュートリアルでは、いくつかの一般的なコネクションプーリングフレームワークの概要を説明し、独自のコネクションプールを最初から実装する方法を学習します。
** 2なぜコネクションプーリングなのか
質問はもちろん修辞的です。
一般的なデータベース接続のライフサイクルに含まれる一連の手順を分析すると、その理由がわかります。
-
データベースドライバを使用してデータベースへの接続を開く
-
TCPソケット
を開きます.
データの読み書き
。ソケットを介したデータの読み書き
-
接続を閉じる
-
ソケットを閉じる
-
データベース接続はかなり高価な操作** であり、そのため、可能な限りすべてのユースケースで最小限に抑える必要があります(エッジの場合は回避するだけ)。
-
これが、コネクションプーリングの実装が活躍する場所です。
データベース接続コンテナを実装するだけで、既存の多数の接続を再利用できるため、膨大な数の高価なデータベース旅行にかかるコストを効果的に節約できるため、データベース駆動型アプリケーションの全体的なパフォーマンスが向上します。
3 JDBC接続プーリングフレームワーク
実用的な観点から見ると、コネクションプールをゼロから実装することは、そこで利用可能な「エンタープライズ対応」のコネクションプーリングフレームワークの数を考えると、まったく意味がありません。
この記事の目標である教訓的なものから、そうではありません。
それでも、基本的な接続プールの実装方法を学ぶ前に、まず、いくつかの一般的な接続プールフレームワークを紹介しましょう。
3.1. Apache Commons DBCP
この簡単なまとめを、フル機能の接続プーリングJDBCフレームワークであるhttps://commons.apache.org/proper/commons-dbcp/download__dbcp.cgi[Apache Commons DBCPコンポーネント]から始めましょう。
public class DBCPDataSource {
private static BasicDataSource ds = new BasicDataSource();
static {
ds.setUrl("jdbc:h2:mem:test");
ds.setUsername("user");
ds.setPassword("password");
ds.setMinIdle(5);
ds.setMaxIdle(10);
ds.setMaxOpenPreparedStatements(100);
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
private DBCPDataSource(){ }
}
この場合は、DBCPのプロパティを簡単に設定するために、静的ブロックを含むラッパークラスを使用しました。
DBCPDataSource
クラスを使用してプールされた接続を取得する方法は次のとおりです。
Connection con = DBCPDataSource.getConnection();
3.2. ひかりCP
それでは、https://brettwooldridge.github.io/HikariCP/[HikariCP]を見てみましょう。https://github.com/brettwooldridge[Brett Wooldridge]によって作成された超高速JDBC接続プーリングフレームワーク(詳細についてはHikariCPを構成して最大限に活用する方法は、https://www.baeldung.com/hikaricp[この記事]を参照してください。
public class HikariCPDataSource {
private static HikariConfig config = new HikariConfig();
private static HikariDataSource ds;
static {
config.setJdbcUrl("jdbc:h2:mem:test");
config.setUsername("user");
config.setPassword("password");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
ds = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
private HikariCPDataSource(){}
}
同様に、
HikariCPDataSource
クラスを使用してプールされた接続を取得する方法は次のとおりです。
Connection con = HikariCPDataSource.getConnection();
3.3. C3PO
このレビューの最後は、Steve Waldmanによって開発されたhttps://www.mchange.com/projects/c3p0/[C3PO]、強力なJDBC4接続およびステートメントプーリングフレームワークです。
public class C3poDataSource {
private static ComboPooledDataSource cpds = new ComboPooledDataSource();
static {
try {
cpds.setDriverClass("org.h2.Driver");
cpds.setJdbcUrl("jdbc:h2:mem:test");
cpds.setUser("user");
cpds.setPassword("password");
} catch (PropertyVetoException e) {
//handle the exception
}
}
public static Connection getConnection() throws SQLException {
return cpds.getConnection();
}
private C3poDataSource(){}
}
予想どおり、
C3poDataSource
クラスを使用してプールされた接続を取得することは、前の例と似ています。
Connection con = C3poDataSource.getConnection();
4簡単な実装
接続プールの基本的なロジックを理解するために、簡単な実装を作成しましょう。
1つのインターフェースに基づいた疎結合設計から始めましょう。
public interface ConnectionPool {
Connection getConnection();
boolean releaseConnection(Connection connection);
String getUrl();
String getUser();
String getPassword();
}
ConnectionPool
インタフェースは、基本的な接続プールのパブリックAPIを定義します。
それでは、プールされた接続の取得や解放など、いくつかの基本機能を提供する実装を作成しましょう。
public class BasicConnectionPool
implements ConnectionPool {
private String url;
private String user;
private String password;
private List<Connection> connectionPool;
private List<Connection> usedConnections = new ArrayList<>();
private static int INITIAL__POOL__SIZE = 10;
public static BasicConnectionPool create(
String url, String user,
String password) throws SQLException {
List<Connection> pool = new ArrayList<>(INITIAL__POOL__SIZE);
for (int i = 0; i < INITIAL__POOL__SIZE; i++) {
pool.add(createConnection(url, user, password));
}
return new BasicConnectionPool(url, user, password, pool);
}
//standard constructors
@Override
public Connection getConnection() {
Connection connection = connectionPool
.remove(connectionPool.size() - 1);
usedConnections.add(connection);
return connection;
}
@Override
public boolean releaseConnection(Connection connection) {
connectionPool.add(connection);
return usedConnections.remove(connection);
}
private static Connection createConnection(
String url, String user, String password)
throws SQLException {
return DriverManager.getConnection(url, user, password);
}
public int getSize() {
return connectionPool.size() + usedConnections.size();
}
//standard getters
}
かなり単純ではありますが、
BasicConnectionPool
クラスは、一般的な接続プールの実装に期待される最小限の機能を提供します。
簡単に言うと、このクラスは10個の接続を格納する
ArrayList
に基づいて接続プールを初期化します。これは簡単に再利用できます。
-
DriverManager
class
およびhttps://docs.oracle.com/でJDBC接続を作成することが可能ですjavase/8/docs/api/javax/sql/DataSource.html[データソース]の実装** 。
接続データベースの作成を不可知論的にしておくほうがはるかによいので、
create()
静的ファクトリメソッド内で前者を使用しました。
この場合は、このメソッドを
BasicConnectionPool
内に配置しました。これがインターフェイスの唯一の実装だからです。
複数の
ConnectionPool
を実装した、より複雑な設計では、それをインターフェースに配置することをお勧めします。したがって、より柔軟な設計とより高いレベルの結束性が得られます。
ここで強調しなければならない最も重要な点は、いったんプールが作成されると
接続はプールから取得されるので、新しいものを作成する必要はないということです
。
さらに、** 接続が解放されると、実際にはプールに戻されるため、他のクライアントはそれを再利用できます。
__Connectionのclose()メソッドへの明示的な呼び出しなど、基盤となるデータベースとのこれ以上のやり取りはありません。
5
BasicConnectionPool
クラスを使用する
予想通り、
BasicConnectionPool
クラスを使用するのは簡単です。
簡単な単体テストを作成し、プールされたメモリ内のhttp://www.h2database.com/html/main.html[H2]接続を取得しましょう。
@Test
public whenCalledgetConnection__thenCorrect() {
ConnectionPool connectionPool = BasicConnectionPool
.create("jdbc:h2:mem:test", "user", "password");
assertTrue(connectionPool.getConnection().isValid(1));
}
6. さらなる改善とリファクタリング
もちろん、私たちのコネクションプーリング実装の現在の機能を微調整/拡張する余地はたくさんあります。
たとえば、
getConnection()
メソッドをリファクタリングし、最大プールサイズのサポートを追加できます。利用可能な接続がすべて使用されていて、現在のプールサイズが設定された最大サイズより小さい場合、このメソッドは新しい接続を作成します。
@Override
public Connection getConnection() throws SQLException {
if (connectionPool.isEmpty()) {
if (usedConnections.size() < MAX__POOL__SIZE) {
connectionPool.add(createConnection(url, user, password));
} else {
throw new RuntimeException(
"Maximum pool size reached, no available connections!");
}
}
Connection connection = connectionPool
.remove(connectionPool.size() - 1);
usedConnections.add(connection);
return connection;
}
メソッドが
SQLException
をスローするようになったことに注意してください。これは、インターフェイスシグネチャも更新する必要があることを意味します。
あるいは、接続プールインスタンスを適切にシャットダウンするためのメソッドを追加することもできます。
public void shutdown() throws SQLException {
usedConnections.forEach(this::releaseConnection);
for (Connection c : connectionPool) {
c.close();
}
connectionPool.clear();
}
実稼働対応の実装では、接続プールは、現在使用中の接続を追跡する機能、準備済みステートメントプーリングのサポートなど、多数の追加機能を提供します。
簡単にするために、これらの追加機能を実装する方法は省略し、明確にするために実装をスレッドセーフにしないようにします。
7. 結論
この記事では、接続プールとは何かを詳しく調べ、独自の接続プールの実装をロールバックする方法を学びました。
もちろん、フル機能のコネクションプーリングレイヤをアプリケーションに追加するたびに、最初から始める必要はありません。
そのため、最も一般的なコネクションプールフレームワークのいくつかを簡単にまとめたので、それらをどのように使用するかについて明確なアイデアを得て、要件に最も適したものを選択できます。
いつものように、この記事に示されているすべてのコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/core-java-persistence[GitHubで利用可能]です。