When I try to do more than one transaction in a JSF page, I get the following error:
A potential connection leak detected for connection pool MSSQL. The stack trace of the thread is provided below :
com.sun.enterprise.resource.pool.ConnectionPool.setResourceStateToBusy(ConnectionPool.java:324)
com.sun.enterprise.resource.pool.ConnectionPool.getResourceFromPool(ConnectionPool.java:758)
com.sun.enterprise.resource.pool.ConnectionPool.getUnenlistedResource(ConnectionPool.java:632)
com.sun.enterprise.resource.pool.AssocWithThreadResourcePool.getUnenlistedResource(AssocWithThreadResourcePool.java:196)
com.sun.enterprise.resource.pool.ConnectionPool.internalGetResource(ConnectionPool.java:526)
com.sun.enterprise.resource.pool.ConnectionPool.getResource(ConnectionPool.java:381)
com.sun.enterprise.resource.pool.PoolManagerImpl.getResourceFromPool(PoolManagerImpl.java:245)
com.sun.enterprise.resource.pool.PoolManagerImpl.getResource(PoolManagerImpl.java:170)
com.sun.enterprise.connectors.ConnectionManagerImpl.getResource(ConnectionManagerImpl.java:338)
com.sun.enterprise.connectors.ConnectionManagerImpl.internalGetConnection(ConnectionManagerImpl.java:301)
com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:190)
com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:165)
com.sun.enterprise.connectors.ConnectionManagerImpl.allocateConnection(ConnectionManagerImpl.java:160)
com.sun.gjc.spi.base.DataSource.getConnection(DataSource.java:113)
cl.codesin.colegios.util.persistencia.DAOManejador.abrir(DAOManejador.java:126)
Please notice the last line I pasted:
cl.codesin.colegios.util.persistencia.DAOManejador.abrir(DAOManejador.java:126)
abrir does the following:
public void abrir() throws SQLException {
try
{
if(this.con==null || this.con.isClosed())
this.con = fuenteDatos.getConnection();
}
catch(SQLException e)
{
throw e;
}
}
It works in a singleton DAO manager this way: the DAO manager has one instance of each DAO and manages a single connection that every DAO shares. When a DAO is requested, it does the following:
public DAORegion getDAOregion() throws SQLException {
try
{
if(con == null) //con is the connection the DAO manager uses
{
this.abrir();
}
}
catch(SQLException e)
{
throw e;
}
if(this.DAOregion==null)
{
this.DAOregion = new DAORegion(this.con);
}
return DAOregion;
}
When closing a connection, the manager just calls to con.close() without anything else.
By the way, I have no persistence.xml since I’m working with JDBC.
What am I doing wrong? Thank you beforehand.
EDIT: By desactivating the leak detection from the Glassfish server I could avoid the exception, however I’m still getting a “Connection closed” error. Worst is, now I don’t know exactly where the error is being thrown.
EDIT 2: I changed my DAO manager again. Here’s the implementation.
public class DAOManejador {
public static DAOManejador getInstancia() {
return DAOManejadorSingleton.INSTANCIA;
}
//This is just a sample, every getDAOXXX works the same.
public DAOUsuario getDAOusuario() throws SQLException {
try
{
if(con == null)
{
this.abrir();
}
}
catch(SQLException e)
{
throw e;
}
if(this.DAOusuario==null)
{
this.DAOusuario = new DAOUsuario(this.con, this.stmt, this.res);
}
return DAOusuario;
}
public void abrir() throws SQLException {
try
{
if(this.con==null || this.con.isClosed())
this.con = fuenteDatos.getConnection();
}
catch(SQLException e)
{
throw e;
}
}
public void iniciaTransaccion() throws SQLException {
try
{
con.setAutoCommit(false);
}
catch(SQLException e)
{
throw e;
}
}
public void cierraTransaccion() throws SQLException {
try
{
con.setAutoCommit(true);
}
catch(SQLException e)
{
throw e;
}
}
public void comprometer() throws SQLException {
try
{
con.commit();
}
catch(SQLException e)
{
throw e;
}
}
public void deshacer() throws SQLException {
try
{
con.rollback();
}
catch(SQLException e)
{
throw e;
}
}
public void cerrar() throws SQLException {
try
{
if(this.stmt!=null && !this.stmt.isClosed())
stmt.close();
if(this.res!=null && !this.res.isClosed())
this.res.close();
if(this.con!=null && !this.con.isClosed())
con.close();
}
catch(SQLException e)
{
throw e;
}
}
public void comprometerYTerminarTransaccion() throws SQLException {
try
{
this.comprometer();
this.cierraTransaccion();
}
catch(SQLException e)
{
throw e;
}
}
public void comprometerYCerrarConexion() throws SQLException {
try
{
this.comprometer();
this.cierraTransaccion();
this.cerrar();
}
catch(SQLException e)
{
throw e;
}
}
//Protegidos
@Override
protected void finalize() throws SQLException, Throwable
{
try
{
this.cerrar();
}
finally
{
super.finalize();
}
}
//Private
private DataSource fuenteDatos;
private Connection con = null;
private PreparedStatement stmt = null;
private ResultSet res = null;
private DAOUsuario DAOusuario = null;
private DAORegion DAOregion = null;
private DAOProvincia DAOprovincia = null;
private DAOComuna DAOcomuna = null;
private DAOColegio DAOcolegio = null;
private DAOManejador() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.fuenteDatos = (DataSource)ctx.lookup("jndi/MSSQL");
}
catch(Exception e){ throw e; }
}
private static class DAOManejadorSingleton {
public static final DAOManejador INSTANCIA;
static
{
DAOManejador dm;
try
{
dm = new DAOManejador();
}
catch(Exception e)
{ dm=null; }
INSTANCIA = dm;
}
}
}
What I did now is to provide a single access point for every DAO. When a DAO wants to use a statement or a resource, they’ll all use the same one. When they need to open again one, the system does the following:
public abstract class DAOGenerico<T> {
//Protected
protected final String nombreTabla;
protected Connection con;
protected PreparedStatement stmt;
protected ResultSet res;
protected DAOGenerico(Connection con, PreparedStatement stmt, ResultSet res, String nombreTabla) {
this.nombreTabla = nombreTabla;
this.con = con;
this.stmt = stmt;
this.res = res;
}
//Prepares a query
protected final void prepararConsulta(String query) throws SQLException
{
try
{
if(this.stmt!=null && !this.stmt.isClosed())
this.stmt.close();
this.stmt = this.con.prepareStatement(query);
}
catch(SQLException e){ throw e; }
}
//Gets a ResultSet
protected final void obtenerResultados() throws SQLException {
try
{
if(this.res!=null && !this.res.isClosed())
this.res.close();
this.res = this.stmt.executeQuery();
}
catch(SQLException e){ throw e; }
}
}
And it still doesn’t work.
I tried not doing anything when closing the connection. I commented the code in thecerrarmethod, and for some reason, it works! Even when it’s a bad practice! Is it okay to keep it like that, or should I find a way to close a connection?Disregard this, I found what’s wrong. I hope someone can make good use of this in the future.
The problem
Each time I try to open a connection, I get a completely brand new connection. What’s the problem with this?
Only when I create a new instance of the DAO I assign it a new connection. What will happen in the following case then?
If, from here, you try to do database stuff, you’ll get a Connection closed error since
daoUserwill still hold the old connection.What I did
I modified the DAO manager class. It no longer has a
getDAOXXX()per DAO, but rather the following:Each time the user requests a DAO, it’ll ask the manager to return the correct type of DAO. But instead of storing each instance, the manager will create new instances depending on the current connection (
conis the connection,stmtis a PreparedStatement andresis a ResultSet – they will be used so they can be closed when the manager closes the connection so nothing leaks).Tablais anenumholding the current table names in the database so it can return the correct DAO. This worked with no problems whatsoever. The rest of the class is the same, so if you want to use it, just replace theDAOUsuariomethod with the one above and it should work fine.