2. JDBC核心API与连接管理

JDBC(Java Database Connectivity)是Java访问关系型数据库的标准规范,它定义了一套接口,由各数据库厂商提供驱动实现。理解JDBC的核心API,是掌握后续ORM框架的基础。本节围绕DriverManager、Connection、Statement、ResultSet四个核心对象,以及连接参数配置、连接池原理与HikariCP集成展开。

一、JDBC核心对象与调用链

一次典型的JDBC查询,本质上是四个对象依次协作的过程:DriverManager负责根据URL找到合适的驱动并建立物理连接,返回Connection;Connection创建Statement(或PreparedStatement)承载SQL;Statement执行后返回ResultSet,以游标方式逐行读取结果。四者的关系与数据流向如下:

JDBC 核心对象调用链与数据流 Java 应用 DAO / Service DriverManager 匹配 URL → 驱动 Connection 物理/逻辑连接 Statement PreparedStatement ResultSet 游标逐行读取 数据库 MySQL / PG 返回结果集 ① 加载驱动 → ② DriverManager.getConnection(url,user,pwd) → ③ conn.prepareStatement(sql) ④ ps.setXxx(index,value) 绑定参数 → ⑤ ps.executeQuery() → ⑥ rs.next() 逐行读取 → ⑦ 关闭资源(try-with-resources)

二、DriverManager与连接参数

DriverManager通过URL前缀匹配已注册的驱动。以MySQL为例,标准URL格式为:

jdbc:mysql://host:3306/dbname?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8

关键参数包括:host/port定位数据库实例,dbname指定默认库,serverTimezone避免时区导致的日期偏移,characterEncoding保证中文不乱码,useSSL控制加密连接。生产环境还应配置connectTimeout和socketTimeout,防止网络异常时线程长时间阻塞。

需要特别注意的是,每次DriverManager.getConnection()都会建立一条物理连接,涉及TCP握手、认证、会话初始化,开销通常在几十毫秒量级。高并发下频繁创建销毁连接会迅速耗尽数据库的连接数上限,这正是连接池存在的根本原因。

三、Statement与ResultSet

Statement用于执行静态SQL,而PreparedStatement是它的子接口,支持预编译和参数占位符?。使用PreparedStatement有两大收益:一是数据库可缓存执行计划,重复执行效率更高;二是参数通过setXxx()绑定,天然防止SQL注入。因此除极少数动态表名场景外,都应优先使用PreparedStatement。

String sql = "SELECT id, name, balance FROM account WHERE balance > ?";
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setBigDecimal(1, new BigDecimal("1000"));
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            long id = rs.getLong("id");
            String name = rs.getString("name");
            BigDecimal balance = rs.getBigDecimal("balance");
            // 处理每一行
        }
    }
}

ResultSet是一个游标,初始位于第一行之前,必须调用next()才能移动到下一行,返回false表示无更多数据。取值时推荐使用列名而非列索引,可读性更好且不易因SQL列顺序调整而出错。对于可能为NULL的列,应使用getObject()或先wasNull()判断,避免基本类型拆箱空指针。

四、连接池原理与HikariCP集成

连接池的核心思想是空间换时间:启动时预先创建若干物理连接放入池中,应用借出使用后归还而非关闭。这样把昂贵的连接建立成本摊薄到多次复用上。连接池需要管理几个关键行为:借出时校验连接是否有效、空闲连接的超时回收、池满时的等待策略、以及连接泄漏检测。

HikariCP是目前性能与稳定性俱佳的连接池实现,Spring Boot 2.x起默认集成。其核心参数如下表:

参数含义示例值
maximumPoolSize池中最大连接数10
minimumIdle最小空闲连接数5
connectionTimeout获取连接的最长等待毫秒数30000
idleTimeout空闲连接存活时间600000
maxLifetime连接最大生命周期,应小于数据库wait_timeout1800000
connectionTestQuery连接有效性检测SQLSELECT 1

一个常见的经验公式是:maximumPoolSize ≈ CPU核数 × 2 + 磁盘数,但更可靠的做法是结合压测结果调整。若maxLifetime大于数据库的wait_timeout,连接可能被数据库单方面断开,应用借出时才发现失效,因此maxLifetime通常设置为比wait_timeout小30秒左右。

HikariCP的connectionTimeout默认30秒,若频繁出现"Connection is not available"异常,应先排查是否存在连接泄漏(借出未归还),再考虑调大池容量。

五、资源关闭与最佳实践

Connection、Statement、ResultSet都实现了AutoCloseable,应使用try-with-resources确保按ResultSet→Statement→Connection的顺序关闭。使用连接池时,conn.close()并非真正关闭物理连接,而是将连接归还池中,因此必须显式关闭,否则连接会被永久占用直至泄漏检测触发。

不要在多线程间共享同一个Connection,JDBC连接不是线程安全的。每个线程应从池中独立获取连接,用完立即归还。

综上,DriverManager负责建立连接,Connection是会话载体,Statement承载SQL,ResultSet承载结果,而连接池则解决了连接复用与资源管控问题。掌握这条主线,后续学习JdbcTemplate、MyBatis等框架时,就能清楚它们究竟在封装哪一层。