SQLException是检查异常,必须显式捕获或声明抛出;应在DAO层转换为语义化自定义异常,用getSQLState()和getErrorCode()精准判错,配合try-with-resources管理资源,批量操作需通过BatchUpdateException处理失败。

SQLException 是检查异常,必须显式处理
Java 中 SQLException 继承自 Exception,属于检查异常(checked exception),编译器强制要求捕获或声明抛出。不处理会直接编译失败,不是“运行时看情况”。
- 不能只靠 IDE 自动生成
try-catch并吞掉异常(比如空catch块),这会让数据库错误静默失败 - 也不建议在方法签名里无差别加
throws SQLException向上甩锅,尤其在 service 层——调用方很难合理响应底层 JDBC 错误 - 推荐在 DAO 层捕获,转换为更语义化的自定义异常(如
DataAccessException),并保留原始SQLException作为 cause
通过 getSQLState() 和 getErrorCode() 区分错误类型
SQLException 的 getSQLState()(标准 SQL 状态码,5 位字符串)和数据库厂商专属的 getErrorCode()(如 MySQL 的 1062、PostgreSQL 的 23505)才是判断错误本质的关键,而不是仅依赖 getMessage() 的文本内容——它可能被本地化或含无关上下文。
-
getSQLState()遵循 SQL:2003 标准,例如"23505"表示唯一约束冲突(PostgreSQL/Oracle 兼容),"23000"是通用完整性约束违例 -
getErrorCode()更具体:MySQL 插入重复主键报错是1062,Oracle 是1,H2 是23505 - 不要用
getMessage().contains("Duplicate")做判断——字段名、语言、版本都可能导致匹配失效
使用 try-with-resources 确保 ResultSet/Statement/Connection 正确关闭
资源未关闭是引发后续 SQLException(如 “Connection closed”、“Operation not allowed after ResultSet closed”)的常见原因,和事务逻辑无关,纯属资源管理疏漏。
-
Connection、Statement、ResultSet都实现了AutoCloseable,必须用 try-with-resources,而非手动finally调用close() - 避免在同一个
try块中复用Statement执行多个查询——旧ResultSet会被自动关闭,再调用其next()就抛SQLException - 连接池(如 HikariCP)返回的
Connection关闭操作实际是归还连接,但依然要 close,否则连接永远不释放
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
// 处理结果
}
} catch (SQLException e) {
// 处理异常
}批量操作失败时,getUpdateCounts() 返回值需谨慎解读
调用 executeBatch() 后,getUpdateCounts() 返回 int[],但它的含义容易误解:不是“每个语句影响行数”,而是“执行状态标识”。Statement.EXECUTE_FAILED(值为 -3)表示该条失败,但其他条可能成功;而 -2 表示“未执行”(某些驱动对部分失败不填具体数值)。
立即学习“Java免费学习笔记(深入)”;
- 不能假设数组长度等于 SQL 条数——某些驱动在遇到第一个失败后就中断,后续条目不计入数组
- 不同驱动行为不一致:MySQL Connector/J 默认继续执行(可配
continueBatchOnError=false),PostgreSQL 的 pgjdbc 则默认中断 - 真正可靠的失败定位方式是捕获
BatchUpdateException,它继承自SQLException,且提供getUpdateCounts()和嵌套的原始异常链
JDBC 异常处理最麻烦的点不在语法,而在于同一类错误(比如唯一键冲突)在不同数据库、不同驱动版本下暴露出来的状态码、错误码、甚至异常类型都可能不同。别迷信文档里的“标准”,上线前务必用目标环境的真实数据库跑一遍边界 case。










