该timeout
变量似乎与连接空闲的时间不对应,而是与池可以等待返回新连接或抛出异常的时间相对应(我看过这个源代码,不知道是不是最新)。我认为跟踪“空闲”连接会相当困难,因为在这种情况下“空闲”的真正含义是什么?您可能希望获得连接以供以后使用。所以我想说,连接池知道您已完成连接的唯一安全close()
方法是调用它。
如果您担心开发团队忘记调用close()
他们的代码,我在下面描述了一种我过去使用过的技术(在我的例子中,我们想跟踪 unclosed InputStream
s,但概念是相同的)。
免责声明:
- 我假设连接仅在单个请求期间使用,并且在连续请求期间不跨越。在后一种情况下,您不能使用下面的解决方案。
- 您的连接池实现似乎已经使用了与我在下面描述的技术类似的技术(即它已经包装了连接),所以我不可能知道这是否适用于您的情况。我没有测试过下面的代码,我只是用它来描述这个概念。
- 请仅在您的开发环境中使用它。在生产中,您应该确信您的代码已经过测试并且行为正确。
如上所述,主要思想是:我们有一个中心位置(连接池),从中我们获取资源(连接),并且我们希望跟踪这些资源是否被我们的代码释放。我们可以使用一个网络,该网络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> _tracker = new ThreadLocal<Tracker>() {
@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<Trackable, Trackable> monitoredResources = new HashMap<Trackable, Trackable>();
// 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<Trackable> 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);
}
}