This answer is for Apache tomcat-jdbc DataSource provider.
First of all you need to have understanding of PoolProperties
setRemoveAbandonedTimeout
setRemoveAbandoned
When a query took longer than the time specified in setRemoveAbandonedTimeout(int) the connection which executing this query is marked as Abandon and java.sql.Connection.close() method is invoked, which will keep waiting for query to complete before releasing the connection.
We can implement our own handler to handle abandoned connection. Below is changes
First of all we need to add an interface
package org.apache.tomcat.jdbc.pool;
public interface AbandonedConnectionHandler {
public void handleQuery(Long connectionId);
}
tomcat-jdbc files changes:
PoolConfiguration.java (interface)
Adding getter and setter method.
public void setAbandonedConnectionHandler(AbandonedConnectionHandler abandonedConnectionHandler);
public AbandonedConnectionHandler getAbandonedConnectionHandler();
Override these methods to all implementation classes
- DataSourceProxy.java
- PoolProperties.java
- org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.java
Add a method getConnectionId() to org.apache.tomcat.jdbc.pool.PooledConnection.java
public Long getConnectionId() {
try {
//jdbc impl has getId()
Method method = this.connection.getClass().getSuperclass().getMethod("getId");
return (Long)method.invoke(this.connection);
} catch (Exception e) {
log.warn(" Abandoned QueryHandler failed to initialize connection id ");
}
return null;
}
The above reflection code might differ in case of different mysql driver.
Now we need to put our handler before calling java.sql.Connection.close() method in org.apache.tomcat.jdbc.pool.ConnectionPool.java
The ConnectionPool.java method which will initiate the abandoned connection cleaner is
protected void abandon(PooledConnection con)
Add below code inside this method before calling release(con);
if(getPoolProperties().getAbandonedConnectionHandler() != null)
{
con.lock();
getPoolProperties().getAbandonedConnectionHandler().handleQuery(con.getConnectionId());
}
Now all you have to do is to pass your handerInstance along with the PoolProperties while creating tomcat-jdbc DataSource.
p.setAbandonedConnectionHandler(new ConnectionHandler(true));
Here is my AbandonedConnectionHandler implementation.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.AbandonedConnectionHandler;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
public class ConnectionHandler implements AbandonedConnectionHandler{
private static final Log log = LogFactory.getLog(ConnectionHandler.class);
private Boolean isAllowedToKill;
private PoolConfiguration poolProperties;
public ConnectionHandler(Boolean isAllowedToKill)
{
this.isAllowedToKill = isAllowedToKill;
}
@Override
public void handleQuery(Long connectionId) {
Connection conn = null;
Statement stmt = null;
if(this.isAllowedToKill)
{
try{
Class.forName(poolProperties.getDriverClassName());
conn = DriverManager.getConnection(poolProperties.getUrl(),poolProperties.getUsername(),poolProperties.getPassword());
Statement statement = conn.createStatement();
ResultSet result = statement.executeQuery("SELECT ID, INFO, USER, TIME FROM information_schema.PROCESSLIST WHERE ID=" + connectionId);
if(result.next())
{
if(isFetchQuery(result.getString(2)))
{
statement.execute("Kill "+connectionId);
}
}
statement.close();
conn.close();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try {
if(stmt != null && !stmt.isClosed())
stmt.close();
} catch (SQLException e) {
log.warn("Exception while closing Statement ");
}
try {
if(conn != null && !conn.isClosed() )
conn.close();
} catch (SQLException e) {
log.warn("Exception while closing Connection ");
}
}
}
}
private Boolean isFetchQuery(String query)
{
if(query == null)
{
return true;
}
query = query.trim();
return "SELECT".equalsIgnoreCase(query.substring(0, query.indexOf(' ')));
}
public PoolConfiguration getPoolProperties() {
return poolProperties;
}
public void setPoolProperties(PoolConfiguration poolProperties) {
this.poolProperties = poolProperties;
}
}