私が直面したインタビューの1つで、接続プーリングを実装するように依頼されました。したがって、アプローチはこれでした:
List
またはHashMap
を作成しますConnectionImpl
クラスのConnectionPoolingImpl
getConnection()
メソッドが呼び出されると、接続参照が返されます。誰かが接続を返したときに(releaseConnection(ConnectionImpl O)
)、同じアプリケーションが接続オブジェクトを再利用しようとしたときに、実装が例外をスローするようにするにはどうすればよいですか?
同じ接続オブジェクトが新しいアプリケーションに返された可能性があり、それを使用できるはずです。
私の見解は、各Connectionimpl
オブジェクトの別の配列の種類の構造でフラグ変数を維持し、その変数を有効な値に設定することです。ユーザーが接続オブジェクトを返すとき、私はそれを無効な値にします。 ConnectionImpl
でのすべての操作について、ユーザーが有効なフラグを持っているかどうかを確認する必要があります。
そのアプローチにあなたは何を言いますか?
プールから「実際の」接続オブジェクトを返すのではなく、クライアントの代わりに、接続ライフサイクルのpool制御を提供するラッパーを返します。
以下からint
値を読み取ることができる非常に単純な接続があると仮定します。
_interface Connection {
int read(); // reads an int from the connection
void close(); // closes the connection
}
_
ストリームから読み取る実装は次のようになります(例外を無視、EOF処理など)):
_class StreamConnection implements Connection {
private final InputStream input;
int read(){ return input.read(); }
void close(){ input.close(); }
}
_
さらに、次のようなStreamConnection
sのプールがあると仮定します(ここでも、例外、同時実行性などを無視します)。
_class StreamConnectionPool {
List<StreamConnection> freeConnections = openSomeConnectionsSomehow();
StreamConnection borrowConnection(){
if (freeConnections.isEmpty()) throw new IllegalStateException("No free connections");
return freeConnections.remove(0);
}
void returnConnection(StreamConnection conn){
freeConnections.add(conn);
}
}
_
ここでの基本的な考え方は問題ありませんが、接続が返されるかどうかはわかりません。また、接続が閉じられてから戻されないことや、別のソースからの接続がまったく返されないこともわかりません。 。
解決策は(もちろん)間接参照の別のレイヤーです。ラッパーConnection
を返すプールを作成します。これは、close()
が呼び出されたときに基になる接続を閉じる代わりに、プールに返します。
_class ConnectionPool {
private final StreamConnectionPool streamPool = ...;
Connection getConnection() {
final StreamConnection realConnection = streamPool.borrowConnection();
return new Connection(){
private boolean closed = false;
int read () {
if (closed) throw new IllegalStateException("Connection closed");
return realConnection.read();
}
void close() {
if (!closed) {
closed = true;
streamPool.returnConnection(realConnection);
}
}
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
};
}
}
_
このConnectionPool
は、クライアントコードがこれまでに目にする唯一のものです。それがStreamConnectionPool
の唯一の所有者であると仮定すると、このアプローチにはいくつかの利点があります。
複雑さが軽減され、クライアントコードへの影響が最小限に抑えられます-接続を自分で開くこととプールを使用することの唯一の違いは、ファクトリを使用してConnection
sを取得することです(これはすでに行っている可能性がありますが、依存性注入を使用している場合)。最も重要なことは、常に同じ方法で、つまりclose()
を呼び出すことによってリソースをクリーンアップすることです。 read
が何をするかを気にしないのと同じように、必要なデータが得られる限り、close()
が何をするかは気にせず、リソースを解放します。主張しました。この接続がプールからのものであるかどうかを考える必要はありません。
悪意のある/誤った使用に対する保護-クライアントは、プールから取得したリソースのみを返すことができます。基になる接続を閉じることはできません。すでに戻ってきた接続は使用できません...など。
「保証された」リソースの返還 -finalize
実装のおかげで、借用したConnection
へのすべての参照が失われた場合でも、プールに返されます(または少なくとも返されるチャンスがあります)。もちろん、接続はこの方法で必要以上に長く保持されます-ファイナライズが実行されることが保証されていないため、おそらく無期限に-しかし、それは小さな改善です。
H2に付属のJdbcConnectionPool
クラス( ここ )を使用することを伝えます(おそらくコピーできます)。 1つを実装しようとしているネジ:)それはトリックの質問かもしれません。
import Java.sql.Connection;
import Java.sql.DriverManager;
import Java.sql.SQLException;
import Java.util.ArrayList;
import Java.util.List;
/** A Connection Pool with 5 Available Connections **/
class ConnectionPool {
private List<Connection>availableConnections =
new ArrayList<Connection>();
private List<Connection>usedConnections = new ArrayList<Connection>();
private final int MAX_CONNECTIONS = 5;
private String URL;
private String USERID;
private String PASSWORD;
/** Initialize all 5 Connections and put them in the Pool **/
public ConnectionPool(String Url, String UserId, String password)
throws SQLException {
this.URL = Url;
this.USERID = UserId;
this.PASSWORD = password;
for (int count = 0; count <MAX_CONNECTIONS; count++) {
availableConnections.add(this.createConnection());
}
}
/** Private function,
used by the Pool to create new connection internally **/
private Connection createConnection() throws SQLException {
return DriverManager
.getConnection(this.URL, this.USERID, this.PASSWORD);
}
/** Public function, used by us to get connection from Pool **/
public Connection getConnection() {
if (availableConnections.size() == 0) {
System.out.println("All connections are Used !!");
return null;
} else {
Connection con =
availableConnections.remove(
availableConnections.size() - 1);
usedConnections.add(con);
return con;
}
}
/** Public function, to return connection back to the Pool **/
public boolean releaseConnection(Connection con) {
if (null != con) {
usedConnections.remove(con);
availableConnections.add(con);
return true;
}
return false;
}
/** Utility function to check the number of Available Connections **/
public int getFreeConnectionCount() {
return availableConnections.size();
}
}
OK、それで私が正しく理解しているなら、あなたの質問は基本的に「スレッドがプールへの接続を返さないことを保証し、それを使い続けることができるのはどうしてですか?」です。 「生の」Connectionオブジェクトを呼び出し元に戻さない場合、答えは基本的に「必要に応じてどこかに制御を置くことができます」です。
実際のチェックでは、特定の瞬間にスレッドが「所有」する各接続にマークを付け、接続を使用するための呼び出し中にこれが常にThread.currentThread()であることを確認する必要があります。
接続を表すために接続のユーザーに返すオブジェクトはそれほど重要ではありません。それは、Connectionの独自のラッパー実装、またはクエリを実行するためのメソッドを備えた他のラッパーオブジェクトである可能性があります。どちらを使用する場合でも、クエリを実行する前に上記のチェックを行う必要があります。セキュリティのために、通常、「生の」任意のSQLの実行を許可するべきではありませんが、すべてのクエリは明確に定義されたPreparedStatementに基づく必要があることに注意してください。したがって、Connectionの実際の実装を返すという特別な強制はありませんが、状況によっては既存のコードの移行に役立つ場合があります(および、または任意のSQLの実行を本当に許可したい場合)。
多くの場合、このチェックをわざわざ行うこともできません。発信者にデータベースにアクセスする手段を渡すので、空港で爆発物をスキャンすることでパイロットが飛行機を建物に衝突させないようにするのと少し似ています。すべての準備ができているので、システムを台無しにする手段があります。追加のチェックを行います。