0

I'm developing a Java-based WebApp which runs on top of JDK-17 and Tomcat 10.0.5 connected to a MySQL 8.0.27 database with mysql-connector-j-8.0.31. There is not problem opening and closing "physical" connections through JDBC. However, when HikariCP is managing connections, I'm falling in errors related with connections availability when I run query updates through CallableStatement and PreparedStatement objects. It is important to mention that I can manage the webapp with no problems using the executeQuery() method from PreparedStatement. The problem occurs with executeUpdate() of PreparedStatement and CallableStatement objects.

This is my JNDI-Resource setup (edited for setting the max pool size and minimum idle with sugested values):

<Resource   name="jdbc/MySQLHikari" 
            auth="Container"
            factory="com.zaxxer.hikari.HikariJNDIFactory"
            type="javax.sql.DataSource"
            
            minimumIdle="5" 
            maximumPoolSize="10"
            
            idleTimeout="120000"
            connectionTimeout="300000"
            leakDetectionThreshold="300000"                
            
            dataSourceClassName="com.mysql.cj.jdbc.MysqlDataSource"
            
            dataSource.serverName="127.0.0.1"
            dataSource.port="3307" 
            dataSource.databaseName="optica?useSSL=false&#38;useUnicode=true&#38;characterEncoding=utf-8"
            dataSource.user="root"
            dataSource.password="root"
            
            dataSource.cachePrepStmts="true"
            dataSource.prepStmtCacheSize="250"
            dataSource.prepStmtCacheSqlLimit="2048"
            dataSource.useServerPrepStmts="true"
            dataSource.useLocalSessionState="true"
            dataSource.rewriteBatchedStatements="true"
            dataSource.cacheResultSetMetadata="true"
            dataSource.cacheServerConfiguration="true"
            dataSource.elideSetAutoCommits="true"
            dataSource.maintainTimeStats="false"
    />

Here is my sample code on how I acquire DB connections from HikariCP:

public class ConexionMySQLProvider
{
    private static HikariDataSource hikariDataSource;
    
    private static void buildContext()
    {
        try
        {
            Context context = (Context) new InitialContext().lookup("java:/comp/env");
            DataSource dataSource = (DataSource) context.lookup("jdbc/MySQLHikari");
            HikariConfig config = new HikariConfig();
            config.setDataSource(dataSource);
            hikariDataSource = new HikariDataSource(config);
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public static Connection getFromPool() throws Exception
    {
        if (hikariDataSource == null)
        {
            buildContext();
        }
        
        return hikariDataSource.getConnection();
    } 
}

And this is an extract of my persistence controller when the problem occurs:

public class ControllerCliente
{
    public int insert(Cliente c) throws Exception
    {        
        String sql =    "{call insertarCliente(?, ?, ?, ?, ?, ?, ?, " + 
                                              "?, ?, ?, ?, ?, ?, ?, ?, " +
                                              "?, ?, ?)}";
                        
        Connection conn = ConexionMySQLProvider.getFromPool();
                
        CallableStatement cstmt = conn.prepareCall(sql);
        
        int idClienteGenerado = -1;
        
        //Next, I fill each required value in the CallableStatement object:
        cstmt.setString(1, c.getPersona().getNombre());
        ...
        
        //Execution of the query:
        cstmt.executeUpdate();
        
        //Recovering of generated ID's:        
        int idClienteGenerado = -1;
        ...
        
        //A        
        cstmt.close();
        conn.close();
        
        //Devolvemos el ID de Cliente generado:
        return idClienteGenerado;
    }
    
    public void delete(int id) throws Exception
    {
        String sql = "UPDATE cliente SET estatus=0 WHERE idCliente=?";
        
        Connection conn = ConexionMySQLProvider.getFromPool();
        
        PreparedStatement pstmt = conn.prepareStatement(sql)
        
        pstmt.setInt(1, id);
        pstmt.executeUpdate();
        
        pstmt.close();
        conn.close();
    }
    
    public List<Cliente> getAll(String filtro) throws Exception
    {
        String sql = "SELECT * FROM v_clientes WHERE estatus=1";        
        Connection conn = ConexionMySQLProvider.getFromPool();
        PreparedStatement pstmt = conn.prepareStatement(sql);
        ResultSet rs = pstmt.executeQuery();        
        List<Cliente> clientes = new ArrayList<>();
        while (rs.next())
            clientes.add(fill(rs));
        rs.close();
        pstmt.close();
        conn.close();        
        return clientes;
    }
    
    private Cliente fill(ResultSet rs) throws Exception
    {
        Cliente c = new Cliente();
        
        //Fill Cliente from ResultSet
        ...
        
        return c;
    }
}

At the moment of this question:

  • I already have read about HikariCP and how to configure it.
  • I tried the try-catch with resources approach with the same result.
  • I downgraded to mysql-connector 5.1.36 but I was unable to connect with MySQL 8.0.27.
  • I tried with the MariaDB JDBC driver with the same result.
  • I switched to C3P0 but result is the same: unavailable connections after several executeUpdate() method calls. So, I continue using and exploring HikariCP 5.0.1.

Constraints:

  • I must to use MySQL 8.X.
  • I need to keep the webapp in top of Java 17 considering a future migration to Java 19.

I hope that you can help me because I was not be able to find information about this issue.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
LiveGrios
  • 1
  • 1
  • You have leakDetectionThreshold set to 300s. Try setting it much lower, say 10000ms. What do you see in the logs? Look for "Apparent connection leak detected" – tgdavies Nov 30 '22 at 03:46
  • And why is your maximum pool size only 1? – tgdavies Nov 30 '22 at 04:10
  • Sorry, maximum pool size was the last value with I tried to check the connection availability. I tried with value up to 30. – LiveGrios Nov 30 '22 at 04:51
  • You are right, setting leakDetectionThreshold to 10000ms, when I call "executeUpdate()" method from CallableStatement, next message appears: ADVERTENCIA [HikariPool-22 housekeeper] com.zaxxer.hikari.pool.ProxyLeakTask.run Connection leak detection triggered for com.mysql.cj.jdbc.ConnectionImpl@7650f9ec on thread HikariPool-24 connection adder, stack trace follows java.lang.Exception: Apparent connection leak detected. I do not understand the cause since I'm calling close() method on database objects correctly. – LiveGrios Nov 30 '22 at 07:04
  • 1
    You need to use try-with-resources to ensure you correctly close the connection (and other resources). In your current code there is no guarantee you actually close the connection if an exception occurs. – Mark Rotteveel Nov 30 '22 at 09:22
  • As an aside, if you want to connect to MySQL 8 with MySQL Connector/J 5.1.x, you need to use a more recent version (5.1.47 or later IIRC). – Mark Rotteveel Nov 30 '22 at 09:27
  • Look at the complete stack trace. It is recorded when the connection is acquired, so it shows which acquisition you are not closing. Please add the complete stack trace to your question. – tgdavies Nov 30 '22 at 10:00
  • @tgdavies, you are right, thank you to your advise, I enable the HikariCP logging level programatically and found the Connection leak in a validation controller that checks the user permisions before to call my persistence controllers. On that permissions checking, the connection was not closed, forcing to create new ones on each check. OMG, it was a very stupid error. Now the application is working as expected. Thank you -tgdavies !!! – LiveGrios Nov 30 '22 at 15:17
  • Glad to help. And as @MarkRotteveel says, just always use try-with -resources, or patterns where the connection provider calls your function, giving it a connection which you do not need to close at all, such as https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html#execute(org.springframework.jdbc.core.ConnectionCallback) or https://docs.jboss.org/hibernate/orm/5.1/javadocs/org/hibernate/Session.html#doReturningWork-org.hibernate.jdbc.ReturningWork- – tgdavies Nov 30 '22 at 21:52

0 Answers0