SQL 执行异常排查 java.sql.SQLException:从 SQLException 说起
在日常开发中,大家应该或多或少都遇到过这种情况:SQL 在本地跑得好好的,一放到服务里执行就报 java.sql.SQLException。很多同学看到这个异常时,第一反应就是“是不是数据库挂了?”。其实绝大多数情况跟数据库无关,而是 SQL 拼接、参数绑定或者日志缺失导致的。
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
前言
在日常开发中,大家应该或多或少都遇到过这种情况:SQL 在本地跑得好好的,一放到服务里执行就报 java.sql.SQLException
。很多同学看到这个异常时,第一反应就是“是不是数据库挂了?”。其实绝大多数情况跟数据库无关,而是 SQL 拼接、参数绑定或者日志缺失导致的。
这篇文章我结合一个小 Demo,带大家看一下 SQLException 的常见原因,以及如何一步步排查。
场景描述:常见的 SQLException 问题
假设我们有一张 users
表,结构很简单:
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
age INT
);
在 Java 项目里写了一个最普通的查询:
String sql = "SELECT * FROM users WHERE username = ? AND age = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, "zhangfei");
ps.setInt(2, 18);
ResultSet rs = ps.executeQuery();
看似没问题,但在真实项目里,很容易因为下面几个问题报 SQLException:
- SQL 拼接错误:比如忘了
AND
,或者参数占位符数量不对。 - 参数绑定异常:明明是数字,结果 setString();或者顺序错了。
- SQL 没有打印日志:导致无法复现真实执行的 SQL。
排查思路:怎么快速锁定问题?
遇到 SQLException 时,不要慌,通常从以下几个角度来排查:
-
打印完整 SQL
很多时候,你以为你执行的是SELECT * FROM users WHERE username = 'zhangfei'
,实际上可能变成了SELECT * FROM users WHERE username = 'null'
。 -
检查参数绑定
确认每个?
是否都被正确赋值,并且类型匹配。 -
用日志记录 SQL
不仅要打印原始 SQL,还要把 参数替换后的 SQL 打出来,方便直接拿去数据库执行。
Demo:带日志的 SQL 执行封装
我们可以写一个简单的工具方法来封装 SQL 执行和日志打印。这样每次执行 SQL 时,都能清晰看到完整的 SQL。
import java.sql.*;
import java.util.Arrays;
public class JdbcHelper {
public static void executeQuery(Connection conn, String sql, Object... params) {
try (PreparedStatement ps = conn.prepareStatement(sql)) {
// 参数绑定
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
// 打印完整 SQL
System.out.println("Executing SQL: " + buildFullSql(sql, params));
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println("User: " + rs.getString("username") + ", Age: " + rs.getInt("age"));
}
}
} catch (SQLException e) {
System.err.println("SQL 执行异常: " + e.getMessage());
e.printStackTrace();
}
}
// 将参数替换到 SQL 中(简易版)
private static String buildFullSql(String sql, Object... params) {
String fullSql = sql;
for (Object param : params) {
String value = (param instanceof String) ? "'" + param + "'" : String.valueOf(param);
fullSql = fullSql.replaceFirst("\\?", value);
}
return fullSql;
}
// Demo 入口
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/testdb", "root", "password");
executeQuery(conn, "SELECT * FROM users WHERE username = ? AND age = ?", "zhangfei", 18);
}
}
运行效果:
Executing SQL: SELECT * FROM users WHERE username = 'zhangfei' AND age = 18
User: zhangfei, Age: 18
一旦 SQL 写错,比如参数缺失,就能立刻在日志里看到:
Executing SQL: SELECT * FROM users WHERE username = 'zhangfei' AND age = null
SQL 执行异常: Unknown column 'null' in 'where clause'
是不是就一目了然了?
结合实际开发的应用
在真实的业务开发中,SQLException 的定位通常会踩到几个坑:
- 多服务场景:调用链太长,不知道 SQL 是在哪个微服务里执行的。
- ORM 框架二次封装:比如 MyBatis,把 SQL 隐藏在 XML 里,导致排查困难。
- 日志打印不全:只打印了原始 SQL,没有参数,运维无法复现。
因此,建议大家在项目里加一个 SQL 拦截器,不论是 MyBatis 的 Interceptor
,还是 JPA 的日志配置,都要确保能拿到 完整 SQL。
总结
java.sql.SQLException
本质上不是“数据库坏了”,而是代码逻辑和 SQL 执行之间的沟通问题。核心思路就是:
- 先把完整 SQL 打印出来
- 确认参数绑定是否正确
- 保证日志可复现
这样基本上 90% 的 SQL 问题都能快速解决。
更多推荐
所有评论(0)