SQL 执行异常排查 java.sql.SQLException:从 SQLException 说起


网罗开发 (小红书、快手、视频号同名)

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括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 表,结构很简单:

sql 复制代码
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    age INT
);

在 Java 项目里写了一个最普通的查询:

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

  1. SQL 拼接错误 :比如忘了 AND,或者参数占位符数量不对。
  2. 参数绑定异常:明明是数字,结果 setString();或者顺序错了。
  3. SQL 没有打印日志:导致无法复现真实执行的 SQL。

排查思路:怎么快速锁定问题?

遇到 SQLException 时,不要慌,通常从以下几个角度来排查:

  1. 打印完整 SQL

    很多时候,你以为你执行的是 SELECT * FROM users WHERE username = 'zhangfei',实际上可能变成了 SELECT * FROM users WHERE username = 'null'

  2. 检查参数绑定

    确认每个 ? 是否都被正确赋值,并且类型匹配。

  3. 用日志记录 SQL

    不仅要打印原始 SQL,还要把 参数替换后的 SQL 打出来,方便直接拿去数据库执行。

Demo:带日志的 SQL 执行封装

我们可以写一个简单的工具方法来封装 SQL 执行和日志打印。这样每次执行 SQL 时,都能清晰看到完整的 SQL。

java 复制代码
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);
    }
}

运行效果:

txt 复制代码
Executing SQL: SELECT * FROM users WHERE username = 'zhangfei' AND age = 18
User: zhangfei, Age: 18

一旦 SQL 写错,比如参数缺失,就能立刻在日志里看到:

txt 复制代码
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 执行之间的沟通问题。核心思路就是:

  1. 先把完整 SQL 打印出来
  2. 确认参数绑定是否正确
  3. 保证日志可复现

这样基本上 90% 的 SQL 问题都能快速解决。

相关推荐
一灯架构2 小时前
90%的人答错!一文带你彻底搞懂ArrayList
java·后端
倔强的石头_2 小时前
从 “存得下” 到 “算得快”:工业物联网需要新一代时序数据平台
数据库
Y4090013 小时前
【多线程】线程安全(1)
java·开发语言·jvm
TDengine (老段)3 小时前
TDengine IDMP 可视化 —— 分享
大数据·数据库·人工智能·时序数据库·tdengine·涛思数据·时序数据
布局呆星3 小时前
SpringBoot 基础入门
java·spring boot·spring
风吹迎面入袖凉4 小时前
【Redis】Redisson的可重入锁原理
java·redis
GottdesKrieges4 小时前
OceanBase数据库备份配置
数据库·oceanbase
w6100104664 小时前
cka-2026-ConfigMap
java·linux·cka·configmap
SPC的存折4 小时前
MySQL 8组复制完全指南
linux·运维·服务器·数据库·mysql