You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HikariCP 5.0.1搭配MySQL 8.0.27执行更新时连接不可用问题

问题描述

我正在开发基于JDK 17、Tomcat 10.0.5的Java Web应用,使用mysql-connector-j-8.0.31连接MySQL 8.0.27数据库。JDBC直接管理物理连接时一切正常,但使用HikariCP管理连接时,调用CallableStatement和PreparedStatement的executeUpdate()会出现连接不可用错误,executeQuery()却能正常运行。

环境信息

  • JDK: 17
  • Tomcat: 10.0.5
  • HikariCP: 5.0.1
  • MySQL: 8.0.27
  • mysql-connector-j: 8.0.31

JNDI资源配置

<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"
    />

连接池获取代码

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();
    } 
}

问题代码片段

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;
        
        // 填充CallableStatement参数
        cstmt.setString(1, c.getPersona().getNombre());
        ...
        
        // 执行更新,此处报错连接不可用
        cstmt.executeUpdate();
        
        // 回收生成的ID
        idClienteGenerado = -1;
        ...
        
        cstmt.close();
        conn.close();
        
        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();
        
        // 从ResultSet填充Cliente对象
        ...
        
        return c;
    }
}

已尝试方案

  • 查阅HikariCP官方配置文档,调整连接池参数(最大连接数、最小空闲数等),无效果;
  • 使用try-with-resources语法自动关闭资源,问题依旧;
  • 降级mysql-connector至5.1.36,无法连接MySQL 8.0.27;
  • 替换为MariaDB JDBC驱动,问题复现;
  • 切换至C3P0连接池,多次执行executeUpdate()后仍出现连接不可用问题,故转回HikariCP 5.0.1。

约束条件

  • 必须使用MySQL 8.X版本;
  • 应用需基于Java 17开发,未来计划迁移至Java 19。

解决建议

1. 修复连接池获取代码的核心错误

你当前的ConexionMySQLProvider存在严重逻辑问题:通过JNDI获取的DataSource已经是HikariCP实例(因为配置了factory="com.zaxxer.hikari.HikariJNDIFactory"),但你又用这个数据源重新创建了一层HikariDataSource,相当于嵌套连接池,直接导致连接管理混乱,这是引发连接不可用的核心原因。

修改后的代码应直接复用JNDI返回的数据源:

public class ConexionMySQLProvider
{
    private static DataSource dataSource;
    
    private static void buildContext()
    {
        try
        {
            Context context = (Context) new InitialContext().lookup("java:/comp/env");
            dataSource = (DataSource) context.lookup("jdbc/MySQLHikari");
        } 
        catch (Exception e)
        {
            e.printStackTrace();
            throw new RuntimeException("初始化数据源失败", e);
        }
    }

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

2. 修正JNDI配置中的数据库参数错误

你的dataSource.databaseName配置里包含了URL参数(?useSSL=false...),这是不符合规范的。databaseName应仅填写数据库名,额外连接参数需单独配置:

<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"
            dataSource.user="root"
            dataSource.password="root"
            dataSource.useSSL="false"
            dataSource.useUnicode="true"
            dataSource.characterEncoding="utf-8"
            
            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"
    />

3. 显式控制连接的自动提交状态

默认JDBC连接的autoCommit为true,但连接池返回的连接可能存在状态异常,建议在获取连接后显式设置:

Connection conn = ConexionMySQLProvider.getFromPool();
conn.setAutoCommit(true);

4. 开启HikariCP日志排查连接泄漏

在Tomcat的logging.properties中添加以下配置,开启HikariCP的DEBUG日志,跟踪连接的创建、获取、归还流程:

com.zaxxer.hikari.level = FINE

内容的提问来源于stack exchange,提问作者LiveGrios

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 07:45:24