平时排查接口问题,十次里有八次都要看SQL。你在IDEA里写Java项目,会写SQL只是基本功,能在日志里把SQL准确捞出来、带上参数还原成一条能直接执行的语句,这才是日常开发中最有价值的技能之一。这篇文章就把我在IDEA里弄出SQL语句的几种方法完整捋一遍,从框架自带的日志配置到插件一键还原,再到Database面板生成脚本和p6spy拦截器,全部覆盖。无论你是刚入行的小白,还是写了好几年业务代码的老手,照着做,基本能覆盖你的所有输出场景。
先说明一下,"IDEA输出SQL语句"这个说法其实包含两层意思。一层是在程序运行过程中,把框架拼接出来的SQL打印到控制台或者日志文件,方便你排错;另一层是直接利用IDEA的数据库工具和插件,把表结构、数据、甚至一段JSON快速变成SQL脚本。这两种需求,对应的方法完全不同,但很多人只知其一,不知道还有更省事的方案。
1. 为什么要在IDEA里折腾“输出SQL语句”这件事
先把动机说透,不然很多人看到标题会觉得自己用不上。实际开发里,你写的代码是"查用户列表",但框架最终生成的可能是三张表关联join再带两个子查询的复杂SQL。排查慢查询、定位数据对不上、清理脏数据的时候,你光看代码根本看不出问题,必须拿到那条真实执行的SQL,粘到数据库客户端里跑一遍,才能复现问题。
另一个高频场景是写测试数据。项目联调时数据库里没几条可用数据,你又不方便每张表手动insert,这时候如果在IDEA里对着一张表就能直接生成批量INSERT语句,填好数据一把梭,效率翻好几倍。
还有一类场景是压测和性能优化。你拿到一条慢SQL,想看看它的执行计划,通常也是从日志里把SQL抄出来,放到数据库工具的Explain里跑。这一步听起来简单,实际操作时如果SQL很长、参数很多,从日志里手动拼完整SQL非常容易出错,而且浪费时间。所以这篇文章的核心目的,就是帮你省掉那些机械、重复、容易错的手工步骤。
下面的方法我都按"配置成本"从低到高来排,你可以根据自己的项目类型直接选。
2. JPA/Hibernate的show-sql:零成本但别忽视的输出方式
很多人用Spring Data JPA开发时,第一反应是"我的SQL呢?"因为框架默认不打印SQL。打开方式其实非常简单,大多数项目只要在配置里加一行就好了。
2.1 show-sql怎么开、日志格式怎么看
如果你的项目是基于Spring Boot的,在application.yml或application.properties里加:
spring: jpa: show-sql: true properties: hibernate: format_sql: true第一行是总开关,控制台就会打印出Hibernate的SQL;第二行是让SQL按格式化输出,不然一行长SQL挤在一起,阅读难度极大。
打开之后,你会看到类似这样的输出:
Hibernate: select u.id, u.name, u.email from users u where u.status=? and u.dept_id=?注意,这里只有SQL模板,问号代表参数,Hibernate默认不会把参数值也打出来。如果你想看到具体绑定的参数值,需要继续配日志级别:
logging: level: org.hibernate.SQL: DEBUG如果你用的是Spring Boot 3.x,Hibernate版本比较高,建议把日志级别换成:
logging: level: org.hibernate.orm.jdbc.bind: TRACE这样控制台才会输出每个问号对应的实际参数值,否则你会看到SQL却不知道参数是什么,排错效率直接打五折。
2.2 提高日志可读性的两个配置
show-sql打开后,默认输出用的是System.out,这意味着它不会进入你的logback或log4j2文件,只出现在IDEA的Run控制台里。如果你需要保存日志文件供事后分析,尽量别只用show-sql,而是配合日志框架一起看。
我实际项目里常用的组合是,关掉show-sql(避免控制台刷屏),只通过日志级别来控制SQL输出:
spring: jpa: show-sql: false properties: hibernate: format_sql: true logging: level: org.hibernate.SQL: DEBUG org.hibernate.type.descriptor.sql: TRACE这样既能保持控制台干净,又能让日志进入统一的日志文件。再配合IDEA的日志控制台颜色区分,SQL语句会自动显示成不同颜色,肉眼定位速度会快很多。
2.3 我的实操心得
JPA的show-sql有个问题,它打出来的SQL和数据库客户端里能执行的SQL有差距。因为它是Hibernate底层生成的,不同的方言(dialect)生成的语句会有细微差别,比如分页SQL在MySQL下的写法是带limit的,而PostgreSQL下是带limit offset的,贴出来跑的时候要注意数据库类型是否一致。
另外,JPA中如果你用了实体关系关联,比如@ManyToOne默认的FetchType是EAGER,日志里会出现多条SQL连续输出的情况,这叫N+1查询。看到这个不用慌,SQL能输出出来本身就是排查的第一步,你正好可以通过这些SQL判断到底发起了多少次查询。
3. MyBatis日志输出:最常用但也最容易被忽略的参数
MyBatis在整个Java生态里占有率极高,它的SQL日志输出方法和JPA完全不同,坑也更多。
3.1 核心配置:log-impl到底填什么
MyBatis的SQL打印核心在一个配置项:mybatis.configuration.log-impl。这个值写成什么,直接决定你在控制台能不能看到SQL。
在Spring Boot的application.yml中配置:
mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置成这个类,MyBatis会把SQL信息通过System.out打印到控制台。这是最轻量、最无脑的方式,适合本地开发快速看SQL。
如果你希望SQL走日志框架,而不是直接输出到控制台,可以换成:
mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl然后配置对应logger级别:
logging: level: com.example.mapper: DEBUG注意,这里的com.example.mapper不要照抄,要换成你自己Mapper接口所在的包名。这是很多新手最容易踩的坑。你把级别写到com.example甚至root上,日志量会大得吓人,而且不好过滤。只针对mapper包开DEBUG,是控制日志噪音的关键。
3.2 日志框架配合:logback/log4j2里的精细控制
如果你的项目用的是logback(Spring Boot默认),只开Logback级别也是不够的,因为MyBatis的日志是分命名空间输出的。每一个Mapper接口对应一个logger名称。比如:
<logger name="com.example.mapper.UserMapper" level="DEBUG"/>但实际开发中一张张表配置logger太累,直接配置整个mapper包范围内的logger就够:
<logger name="com.example.mapper" level="DEBUG"/>这样配置后,执行MyBatis的查询时,日志会输出Preparing和Parameters两行:
==> Preparing: select * from users where id = ? and name = ? ==> Parameters: 1(String), 张三(String)比JPA的日志要直观得多,参数值也带了类型。这里算是我个人最喜欢的输出形态,一眼就能看出SQL模板和参数值。
3.3 实测对比,我给的配置模板
最终给你一个可以直接抄的完整配置:
mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl mapper-locations: classpath:mapper/*.xml logging: level: com.example.mapper: DEBUG com.example.service: WARN这里把service层保持WARN级别,避免mybatis信息被淹没。如果你还用了PageHelper分页插件,可能需要再把com.github.pagehelper的logger打开,才能看到Count SQL和Page SQL分别执行的情况,这对于排查分页总数不对的问题非常有用。
4. MyBatis Log Plugin:从日志到可执行SQL的一键还原
前面的输出方式都有一个痛点:SQL和参数是分开的,Preparing一行、Parameters一行。你需要人工把问号替换成参数值才能拿到一条完整SQL。参数少还好说,参数一多,替换过程非常痛苦。这时候,IDEA插件MyBatis Log Plugin就能派上大用场。
4.1 插件的定位和安装
MyBatis Log Plugin可以直接把框架打印出来的日志自动拼成一条可以执行的SQL,并且单独显示在一个Log窗口里。它本质上是解析IDE控制台里的日志文本,把Preparing和Parameters的内容合并,再把参数填回占位符。
安装方式很简单:IDEA菜单栏打开Settings -> Plugins -> Marketplace,搜索"MyBatis Log",直接安装即可。插件有社区版也有付费版,免费版已经够日常使用,付费版多了一些历史记录保留和自动复制的功能。我建议先从免费版开始用,顺手了再决定要不要升级。
4.2 使用步骤和效果展示
安装完成后什么都不用改,你的项目只要按上面配置开启了MyBatis日志输出,在Run窗口执行接口以后,MyBatis Log窗口会自动收集日志并渲染成可执行的SQL。
插件界面上会有几个按钮,最常用的是"Format"和"Copy"。点击Format可以美化SQL排版,点击Copy就是直接复制完整的SQL语句。你完全可以做到,接口请求进来的那一刻,SQL就生成好放在剪贴板里,直接粘到Navicat或者IDEA自带的Database工具里跑,中间不需要任何手工替换。
4.3 插件失效、日志错乱这些坑
插件虽好,但有一些前提条件要满足:
第一,插件解析的是MyBatis的日志输出,所以前提是你的项目已经开启了MyBatis日志,而且日志格式是标准的Preparing + Parameters。如果你用了log-impl: StdOutImpl,控制台能看,但插件可能识别不到,建议用Slf4jImpl并配置logger级别。
第二,如果你的项目用了连接池的SQL日志,比如Druid的StatFilter日志,或者HikariCP自带的debug日志,插件是解析不了的,因为格式不对。你需要保持MyBatis自己的日志输出。
第三,IDEA升级到新版本后,部分旧版插件可能会失效。如果发现Log窗口不出现SQL了,先看插件是否支持你的IDEA版本,再看是否要更新插件版本。这个是最常见的兼容性问题,排查第一站就是插件的Release Notes。
5. IDEA Database面板:把表结构、数据直接变成SQL脚本
很多人以为IDEA的Database工具只是用来连数据库跑SQL的,其实它有一个非常强大的功能:生成SQL脚本。这在建表、造数据、同步数据结构时非常省事。
5.1 从表结构生成DDL
在IDEA右侧打开Database面板,连接上你的数据库,找到某张表,右键选择"SQL Scripts -> Generate DDL"。
这样,IDEA会根据表结构生成完整的建表语句,包括字段、类型、主键、索引、外键以及表注释。拿到的DDL可以直接在新环境建表,也可以用于代码评审时展示表变更。
这个功能对比Navicat的转储SQL,好处是它只生成一张表的结构,不会把权限、用户、视图等无关信息带出来,干净利落,适合开发过程中做增量变更。
5.2 直接生成数据脚本(INSERT语句)
表结构能生成,数据也能生成。同样是右键表名,选择"Import Data from Files",或者用Data Editor打开表数据后,右键选择"Generate SQL -> Insert Statements"。
举个例子,我需要给测试环境造一批用户数据,我先在临时表里手动插入几条记录,然后用这个功能一键生成INSERT脚本,再发给同事在另一套环境执行。比手动写一堆insert要快得多,还能避免字段写错。
更实用的一点是,这个功能支持选中多行数据生成INSERT,生成结果带完整字段列表,可读性很高。如果你需要定期把A环境的数据同步到B环境,这个方法可以帮你省去安装额外同步工具的麻烦。
5.3 像连数据库客户端一样用Console执行SQL
IDEA的Database面板里也可以打开一个"Console"执行SQL语句,这本身就是IDEA提供的数据库客户端功能。在这个Console里,你可以直接编写和执行SQL,查看执行计划和结果集。如果你不想为了看一条SQL就打开Navicat或其他客户端,直接在IDEA里就能完成全部操作。
对于从日志或插件里复制出来的SQL,我一般的处理流程是:切到IDEA的Database Console,粘贴SQL,选中部分或全部执行。执行完成后还可以右键点击结果集,选择"Export Data"导出CSV或JSON文件,直接就能作为接口返回数据的mock数据源。
Console加Export Data这个组合,是我实测下来处理SQL结果最顺的流程,特别是需要把SQL结果转成JSON给前端联调时,一步到位。
6. p6spy拦截器:应用运行时输出最完整的SQL
前面说的几种方法有个共同前提,SQL框架必须是JPA或MyBatis。但如果你用的是Spring JDBC Template,甚至是一个内部封装过的不支持SQL输出的老框架,那日志中就很难拿到SQL了。这时p6spy就派上用场了。
6.1 p6spy是什么、和在框架层打印有何不同
p6spy是一个数据库连接驱动代理,它拦截JDBC层面的所有操作,任何PreparedStatement执行时的SQL语句、参数值、执行耗时,它都能抓取到并输出。也就是说,它不关心你的上层用的是MyBatis还是Hibernate,甚至不关心你是不是用了DAO框架,只要最后是JDBC执行的,它都能打印出来。
相比show-sql和MyBatis日志,p6spy最大的优势是直接输出"带参数的完整SQL",不需要再手工拼参数,而且能看到每个语句的执行耗时和执行次数。这对定位"哪条SQL跑了多久"特别有用。
6.2 集成与配置步骤
引入依赖:
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>然后在Spring Boot的配置里,替换数据源驱动:
spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:mysql://localhost:3306/test这里有个重点:url前缀必须改成jdbc:p6spy:,后面再接真正的JDBC地址。
再创建spy.properties配置文件放在classpath下,内容是:
appender=com.p6spy.engine.spy.appender.Slf4JLogger logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat customLogMessageFormat=%(executionTime)ms | %(sql)然后重启项目,执行任意数据库操作,日志里就会看到类似这样的内容:
12ms | select id,name,email from users where id = 123 and status = 1好的一点是参数已经完全替换进去了,而且时间显示在前面,方便判断慢SQL。
6.3 我的取舍建议
p6spy的缺点是多一层代理,有轻微的性能损耗。所以生产环境我不建议开启,但在测试环境、联调环境、压测环境,它是排查问题的利器。
另外,p6spy默认会把所有SQL都打印出来,包括连接池的验证查询(比如select 1),如果你的项目配置了连接池的testOnBorrow,日志会非常啰嗦。解决办法是在spy.properties里配置exclude项,把select 1这一类不需要的SQL排除掉:
exclude=select 1,sqlite_sequence这样日志噪音会小很多,真正要排错的时候,眼睛不会花。
7. IDEA内置的杂项技巧:格式化、JSON转SQL、执行计划里的SQL
上面六种方法已经覆盖了绝大多数"输出SQL"的场景。但还有一些IDEA内置的小功能,虽然不是严格意义的日志输出,但对SQL的生成、整理、分析有奇效,我也一并分享一下。
7.1 SQL格式化与多行重排
从日志、插件里复制出来的SQL,大概率是一行或者几行挤在一起的,直接阅读和用于diff都不方便。IDEA自带了SQL方言识别功能,你把SQL粘贴到IDEA中,在File -> Settings -> Languages & Frameworks -> SQL Dialects里设置好数据库类型,然后按Ctrl + Alt + L,就能自动格式化SQL。
这个功能最好用的点是,IDEA能把复杂的嵌套子查询、join条件、case when语句自动缩进和对齐,比你手动排版快几个数量级。而且格式化之后再用Database面板的执行计划分析,阅读体验完全不同。
7.2 从JSON快速生成INSERT语句
造测试数据时,如果手头已经有了一份JSON格式的数据,手动写INSERT语句很烦。IDEA里的Generate POJOs之类的插件生态中,有类似"JSON转SQL"的小工具,可以实现将JSON字段快速映射成SQL的INSERT语句。虽然这不是IDEA原生功能,但搜索SqlGenerator、JsonToSql这些插件就能找到。
我个人常用的另一个替代方案是:先用Database面板生成一条insert模板,再用工具批量把值替换进去。本质上思路是一样的,都是减少重复劳动。
7.3 执行计划里的SQL
当你拿到一条慢SQL,想分析它为什么慢的时候,直接打开Database Console,输入EXPLAIN关键字,把SQL附在后面,运行结果会显示访问类型、扫描行数、索引使用情况。这是排查慢SQL最直接的手段。IDEA会把这部分结果以表格形式展示,比mysql命令行看起来清爽不少。
需要注意的是,不同数据库的EXPLAIN语法不一样,MySQL支持EXPLAIN SELECT,而SQL Server需要的是SET SHOWPLAN_ALL ON或者直接在客户端里看"显示估计的执行计划"。用IDEA时先确认你的连接类型,再选择合适的语法,不然会报语法错误。
8. 常见问题排查与避坑实战
无论你选哪种方式,实际操作里总会遇到一些问题。我把自己踩过的坑和排查过的反馈整理成下面这张速查表。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 控制台有运行日志但没有SQL | 框架没开启SQL输出,或日志级别不够 | 检查show-sql/log-impl配置,确认logger级别设为DEBUG或TRACE |
| SQL输出了,但参数全是问号 | 框架或日志配置只打印Preparing,不打印参数 | MyBatis要开Parameters日志,Hibernate要开org.hibernate.type或bind的TRACE |
| 复制出来的SQL手动替换参数时报错 | 参数类型不一致,比如字符串类型不带引号 | 使用MyBatis Log Plugin或p6spy自动拼接,避免手工操作 |
| 日志量太大刷屏,找不到自己的SQL | mapper包级别开太高 | 只针对具体mapper包或类设置DEBUG,日志框架级别保持INFO |
| MyBatis Log窗口没有内容 | 插件解析不到日志 | 确认log-impl用的是Slf4jImpl且日志输出到控制台,或检查IDEA版本兼容性 |
| p6spy导致启动失败 | driver和url配置不匹配 | 检查driver-class-name为P6SpyDriver,且url前缀为jdbc:p6spy: |
| Hibernate 6.x输出SQL格式和之前不一样 | 新版本日志输出结构变化 | 使用logging.level.org.hibernate.orm.jdbc.bind: TRACE打印参数 |
| 日志中SQL和参数顺序对应不上 | MyBatis插件或日志框架干扰 | 关闭其他SQL日志插件,只保留一种输出方式 |
| 生成的DDL和数据库实际结构不一致 | 主键、注释、字段类型映射不全 | 建议直接对数据库反向同步,改完结构再生成DDL |
8.1 控制台有日志但没有SQL
这个是最常见的问题。很多项目接了全局日志AOP,把入参出参都打出来了,但其实MyBatis的SQL根本没开。如果用的是MyBatis,请重点确认log-impl配置;如果用的是JPA,请确认show-sql或日志级别是否真的生效。注意Spring Boot配置文件的优先级,application.properties和application.yml同时存在时,后者可能会导致前者配置失效。
8.2 参数值类型和MySQL类型不匹配
这是我在联调时被坑过最多次的坑。比如插入时间字段,日志里显示参数是字符串类型,但数据库要求的是TIMESTAMP,如果直接用日志里的字符串SQL去执行,数据库很可能会报类型转换错误。所以和DBA协作时,拿到带参数的SQL后最好确认参数类型,或者干脆用p6spy那种已经带类型的输出,减少沟通损耗。
8.3 日志太长被控制台截断
IDEA控制台默认输出长度有限,很长的SQL语句会被截断。解决办法是在Help -> Edit Custom Properties里加一行:
idea.cycle.buffer.size=disabled重启IDEA后控制台就不会截断了。这招很适合处理那种拼接了很多where条件的长SQL,不然你只能看到一半SQL,无从排查。
8.4 多行SQL拼接错乱
MyBatis日志输出时,如果SQL里含有换行,控制台显示会多出很多空格和换行。这是MyBatis的日志输出格式决定的,不是你的SQL写错了。遇到这种情况,建议直接使用插件还原后的SQL,或者把SQL粘到IDEA里格式化一遍,再看具体内容。
8.5 插件失效情况和IDEA版本兼容
MyBatis Log Plugin对IDEA新版本支持有一定延迟。每次IDEA大版本升级后,插件市场可能提示兼容性问题。如果实在等不到插件更新,可以退一步用p6spy临时替代,或者升级到最新的IDEA企业版,在Log Output里直接用"Analyze Stacktrace"特长。
我个人在实际项目中的做法是:Spring Boot + MyBatis项目,直接用MyBatis Slf4jImpl配logback,关键排错时打开MyBatis Log Plugin;Spring Boot + JPA项目,优先用日志框架的DEBUG级别输出SQL和参数;联调环境排查慢SQL、特别是涉及多表关联和子查询时,直接上p6spy,一次拿到完整SQL和耗时数据。这套组合用了很久,基本覆盖了日常开发中会遇到的所有场景。
最后再分享一个实用小技巧:如果你经常需要把SQL发给别人,或者收集到自己的SQL笔记库里,可以在IDEA的Database Console里设置默认连接名,这样SQL执行时自带当前库名,复制出去执行的时候就不会出现"数据库没选对"这种低级错误。这个细节看着不起眼,但在多人协作时能省掉很多无谓的沟通成本。