timeout變量似乎不對應于連接空閑的時間,而是對應于池等待返回新連接或拋出異常的時間(我看了一下這個源代碼 ,不知道是不是已是最新)。 我認為跟蹤“空閑”連接是相當困難的,因為在這種情況下“空閑”真正意味著什么? 您可能希望獲得連接以供以后使用。 所以我想說連接池知道你完成連接的唯一安全方法就是調用close() 。
如果你擔心開發團隊忘記在他們的代碼中調用close() ,有一種技術我在下面描述并且我過去曾經使用過(在我的例子中我們想要跟蹤未閉合的InputStream但概念是相同)。
免責聲明:
我假設連接僅在單個請求期間使用,并且在連續請求期間不跨越。 在后一種情況下,您無法使用下面的解決方案。
您的連接池實現似乎已經使用了與我在下面描述的技術類似的技術(即它已經包裝了連接),所以我不可能知道這是否適用于您的情況。 我沒有測試下面的代碼,我只是用它來描述這個概念。
請僅在您的開發環境中使用它。 在生產中,您應該確信您的代碼已經過測試并且行為正確。
如上所述,主要思想是:我們有一個中心位置(連接池),我們從中獲取資源(連接),我們希望跟蹤我們的代碼是否釋放了這些資源。 我們可以使用一個Web Filter ,它使用一個ThreadLocal對象來跟蹤請求期間使用的連接。 我將此類命名為TrackingFilter ,跟蹤資源的對象是Tracker類。
public class TrackingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Tracker.start();
try {
chain.doFilter(request, response);
} finally {
Tracker.stop();
}
}
...
}
為了使Tracker能夠跟蹤連接,每次使用getConnection()獲取連接時以及每次使用close()調用關閉連接時都需要通知它。 為了能夠以對代碼的其余部分透明的方式執行此操作,我們需要包裝ConnectionPool和返回的Connection對象。 您的代碼應該返回新的TrackingConnectionPool而不是原始池(我假設訪問連接池的方式是在一個地方)。 這個新池將依次包裝它提供的每個Connection ,作為TrackableConnection 。 TrackableConnection是知道如何在創建和關閉時通知我們的Tracker的對象。
當您在請求結束時調用Tracker.stop()時,它將報告尚未調用close()所有連接。 由于這是一個按請求操作,您將只識別錯誤操作(即在“創建新產品”功能期間),然后希望您能夠跟蹤那些保留打開連接并修復它們的查詢。
您可以在下面找到TrackingConnectionPool , TrackableConnection和Tracker類的代碼和注釋。 為簡潔起見,遺漏了代表方法。 我希望有所幫助。
注意:對于包裝器使用自動IDE功能(如Eclipse的“生成委托方法”),否則這將是一個耗時且容易出錯的任務。
//------------- Pool Creation
ConnectionPool original = new ConnectionPool(String dbpoolName, ...);
TrackingConnectionPool trackingCP = new TrackingConnectionPool(original);
// ... or without creating the ConnectionPool yourself
TrackingConnectionPool trackingCP = new TrackingConnectionPool(dbpoolName, ...);
// store the reference to the trackingCP instead of the original
//------------- TrackingConnectionPool
public class TrackingConnectionPool extends ConnectionPool {
private ConnectionPool originalPool; // reference to the original pool
// Wrap all available ConnectionPool constructors like this
public TrackingConnectionPool(String dbpoolName, ...) {
originalPool = new ConnectionPool(dbpoolName, ...);
}
// ... or use this convenient constructor after you create a pool manually
public TrackingConnectionPool(ConnectionPool pool) {
this.originalPool = pool;
}
@Override
public Connection getConnection() throws SQLException {
Connection con = originalPool.getConnection();
return new TrackableConnection(con); // wrap the connections with our own wrapper
}
@Override
public Connection getConnection(long timeout) throws SQLException {
Connection con = originalPool.getConnection(timeout);
return new TrackableConnection(con); // wrap the connections with our own wrapper
}
// for all the rest public methods of ConnectionPool and its parent just delegate to the original
@Override
public void setCaching(boolean b) {
originalPool.setCaching(b);
}
...
}
//------------- TrackableConnection
public class TrackableConnection implements Connection, Tracker.Trackable {
private Connection originalConnection;
private boolean released = false;
public TrackableConnection(Connection con) {
this.originalConnection = con;
Tracker.resourceAquired(this); // notify tracker that this resource is aquired
}
// Trackable interface
@Override
public boolean isReleased() {
return this.released;
}
// Note: this method will be called by Tracker class (if needed). Do not invoke manually
@Override
public void release() {
if (!released) {
try {
// attempt to close the connection
originalConnection.close();
this.released = true;
} catch(SQLException e) {
throw new RuntimeException(e);
}
}
}
// Connection interface
@Override
public void close() throws SQLException {
originalConnection.close();
this.released = true;
Tracker.resourceReleased(this); // notify tracker that this resource is "released"
}
// rest of the methods just delegate to the original connection
@Override
public Statement createStatement() throws SQLException {
return originalConnection.createStatement();
}
....
}
//------------- Tracker
public class Tracker {
// Create a single object per thread
private static final ThreadLocal _tracker = new ThreadLocal() {
@Override
protected Tracker initialValue() {
return new Tracker();
};
};
public interface Trackable {
boolean isReleased();
void release();
}
// Stores all the resources that are used during the thread.
// When a resource is used a call should be made to resourceAquired()
// Similarly when we are done with the resource a call should be made to resourceReleased()
private Map monitoredResources = new HashMap();
// Call this at the start of each thread. It is important to clear the map
// because you can't know if the server reuses this thread
public static void start() {
Tracker monitor = _tracker.get();
monitor.monitoredResources.clear();
}
// Call this at the end of each thread. If all resources have been released
// the map should be empty. If it isn't then someone, somewhere forgot to release the resource
// A warning is issued and the resource is released.
public static void stop() {
Tracker monitor = _tracker.get();
if ( !monitor.monitoredResources.isEmpty() ) {
// there are resources that have not been released. Issue a warning and release each one of them
for (Iterator it = monitor.monitoredResources.keySet().iterator(); it.hasNext();) {
Trackable resource = it.next();
if (!resource.isReleased()) {
System.out.println("WARNING: resource " + resource + " has not been released. Releasing it now.");
resource.release();
} else {
System.out.println("Trackable " + resource
+ " is released but is still under monitoring. Perhaps you forgot to call resourceReleased()?");
}
}
monitor.monitoredResources.clear();
}
}
// Call this when a new resource is acquired i.e. you a get a connection from the pool
public static void resourceAquired(Trackable resource) {
Tracker monitor = _tracker.get();
monitor.monitoredResources.put(resource, resource);
}
// Call this when the resource is released
public static void resourceReleased(Trackable resource) {
Tracker monitor = _tracker.get();
monitor.monitoredResources.remove(resource);
}
}