news 2026/9/19 1:13:30

IDEA中输出SQL的完整指南:从MyBatis日志到Druid监控

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IDEA中输出SQL的完整指南:从MyBatis日志到Druid监控

我经常遇到这样的咨询:项目跑起来了,接口也通了,但控制台就是看不到SQL;或者MyBatis把SQL和参数分两行打印,想复制到Navicat里直接跑,还得手动替换那一堆问号;还有人用的是JPA,开了show-sql却只看到一句Hibernate: select...,参数值完全无迹可寻。这篇文章就把IDEA里输出SQL语句的几条路一次性理清楚,从项目日志配置、IDEA自带数据库控制台,到各种插件和第三方监控组件,每条路的原理、配置和避坑点都会讲到。不管你用的是MyBatis还是JPA,还是单纯想在IDEA里手写SQL做验证,都可以按图索骥。

1. SQL不打印或打印不全:先摸清日志链路去了哪

很多人第一反应是"我代码里写了SQL,控制台就该打印出来"。这个想法一半对一半错。SQL能不能出现在控制台,取决于框架有没有把SQL交给日志系统,以及日志系统的级别允不允许它出现。排查之前,先搞清楚这两件事。

1.1 连SELECT都不打印,八成是日志配置压根没生效

最常见的场景是:Spring Boot项目引入了mybatis-spring-boot-starter,结果控制台一条SQL都没有。检查顺序通常是这样的:

先看有没有引入日志门面。现在绝大多数项目是Spring Boot默认的Logback + SLF4J组合,spring-boot-starter-web里已经带上了这些依赖,所以"没引入SLF4J"这个原因在Spring Boot项目里反而不常见。更常见的问题是MyBatis不知道用哪个日志实现。

MyBatis内部有一套日志适配逻辑,它会按固定顺序去ClassPath里找日志框架:SLF4J → Apache Commons Logging → Log4j2 → Log4j → JDK logging → 还有它自己的StdOutImpl。如果项目里存在多个日志框架,或者MyBatis找到了一个日志框架但级别不对,SQL就可能被吞掉。

我见过一个比较典型的配置:

mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

StdOutImpl的好处是它不走日志框架,直接往标准输出里打,在IDEA的控制台一定能看到。缺点是它的输出格式不受你的logback或log4j2配置约束,生产环境不好控制,而且日志级别也没法通过logging.level来动态调。所以我的建议是:

mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

配合下面的日志级别配置一起用,这样SQL日志的控制权就完全交回给项目自己的日志体系了。

1.2 只看到Preparing和Parameters,看不到完整SQL很正常

如果你已经能够看到类似这样的日志:

==> Preparing: SELECT * FROM user WHERE age > ? AND city = ? ==> Parameters: 18(Integer), 北京(String) <== Columns: id, name, age, city <== Row: 1, 张三, 19, 北京

这说明日志链路已经通了。但很多人会问一个问题:为什么MyBatis不直接打印一条完整的SQL,非得拆成模板和参数两段?

因为MyBatis用的是JDBC的PreparedStatement预编译机制,SQL模板先发给数据库做预编译,参数再通过setIntsetString这些方法绑定进去。框架层面确实拿不到一条"拼好的完整SQL",它只能分别打印模板和参数。数据库那边把SQL模板和参数合起来执行,但这个"合起来"的动作发生在数据库服务端,MyBatis自己看不到。

这个设计是刻意的——预编译能防SQL注入又能复用执行计划。所以别再纠结为什么MyBatis不帮你拼SQL了,从代码安全角度来说,拆开打印反而是对的。

1.3 最容易被忽略的坑:多数据源或ORM混用

有些项目里既有MyBatis又有JPA,或者配置了多个数据源,这时候日志配置就要分开看。

如果你用的是MyBatis,日志是打在你的Mapper接口上的,logging.level要配置到Mapper所在的包或具体接口,比如:

logging: level: com.example.project.mapper: debug

如果你只在application.yml里写了一个全局logging.level.root: info,那Mapper包下的debug日志就不会被打印出来。

如果你用的是JPA/Hibernate,日志是打在Hibernate的类上的,配置的key又不一样(后面第4节会详细说)。很多人以为配了一大堆就能全打出SQL,结果MyBatis的SQL出来了,JPA的没有,或者反过来,原因就在这里——两套框架的logger名称体系不同,得分别配置。

2. IDEA数据库控制台:写SQL和验证SQL的"自带考场"

如果目标只是"手写一条SQL看看能不能跑通、结果对不对",那IDEA自带的数据库工具可能比任何日志方案都直接。IDEA Ultimate和免费的IntelliJ IDEA Community版本里可以通过插件方式安装Database插件(DataGrip的内嵌版),连接数据库后在集成的SQL控制台里直接执行语句。

2.1 连接数据库并打开SQL Console

打开IDEA右侧的Database面板,点加号,选择你的数据源类型。这里有个小细节:如果你本机装的是MySQL 8.x,驱动版本建议选8.0以上的,否则连接时会报时区错误或认证方式不兼容。连接串里的serverTimezone参数如果不确定,可以直接在URL后面加?serverTimezone=UTC&useSSL=false,能少踩好几个坑。

连接成功后,右键数据源或某个表,选择New → Query Console,就打开了一个SQL控制台。这个控制台自带智能提示,表名、字段名、函数都能补全,写完SQL后按快捷键Ctrl+Enter(Windows)或Cmd+Enter(Mac)就能执行。

我平时用这个控制台做两件事:一是验证新写的复杂SQL能不能跑通,二是查看表结构。IDEA的控制台还可以在结果表格里直接编辑数据,删除行、修改字段值都会生成对应的UPDATE或DELETE语句,比开Navicat或DBeaver再拖一遍要顺手。

2.2 把"日志里拼好的SQL"粘贴进来跑

回到第1节的问题——日志里只有Preparing和Parameters,怎么快速验证这条SQL?我的习惯是先在IDEA控制台里建一个本地测试场景:把SQL模板复制进去,再把Parameters里的参数一个个替换到问号位置,然后执行。

听着有点原始,但实际挺快。因为IDEA控制台的SQL编辑区可以直接格式化(Ctrl+Alt+L),粘贴过来的乱糟糟的SQL会被整理成可读性很好的格式,再用查找替换把?换掉,半分钟就能跑起来。

如果你用的是MyBatis Log Plugin这类插件(第3节会介绍),它能直接生成完整可执行的SQL文本,然后你选中那行SQL,右键选择Execute in Database Console,IDEA还能自动把这条SQL灌进控制台执行。这两个工具链配合起来非常顺滑。

2.3 用Explain看执行计划来辅助调优

写SQL不只是"能跑"就行,有时候还得看它跑得快不快。IDEA数据库控制台对执行计划的支持相当不错。选中SQL后右键,选择Explain Plan(或者直接按Ctrl+Shift+Enter),就能看到这条SQL的执行计划。

执行计划里最重要的几个列是type(连接类型)、key(实际使用的索引)、rows(预估扫描行数)。我在实际排查慢SQL时,看到type=ALL全表扫描,基本就知道要建索引了;看到key字段为空,就知道连索引都没用上。IDEA执行计划默认有图形化展示和表格两种视图,把SQL拿到这里分析,有时候比去数据库命令行工具里看更直观。

3. MyBatis场景:从一行Preparing到完整可执行语句

大部分Spring Boot + MyBatis项目的SQL输出问题,都可以拆成两步来解决:先把SQL打到控制台,再把控制台日志转成能直接执行的完整SQL。下面按这个顺序说。

3.1 配置日志实现并打开Debug级别

有两种常见的配置方式,先区分一下。

第一种,在application.yml里通过MyBatis配置项指定日志实现:

mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

第二种,用Spring Boot标准的日志级别配置:

logging: level: com.example.project.mapper: debug

这两个配置到底怎么互相作用?实际上,当你设置了log-impl为Slf4jImpl后,MyBatis就会把SQL日志交给SLF4J,日志名是com.example.project.mapper.UserMapper(也就是Mapper接口的全限定名)。这时候如果logging.level里把对应包的级别设成了debug,日志就会显示出来。

有些教程只写了第一种配置就完事了,结果读者复制过去发现还是没有SQL,就是因为漏了logging.level这一项。反过来,如果你已经用Spring Boot的logging.level把Mapper包调成了debug,但log-impl没设置,MyBatis也会自动找到SLF4J并打印,因为它的自动探测机制会生效。所以严格来说,两处配置你可能只需要写一处——但为了保险,也为了让后人看得懂,我建议两处都写清楚。

3.2 为什么MyBatis的SQL日志要打在Mapper接口上

这一点值得单独说,因为理解它之后,你就不会再配错logging.level的路径了。

MyBatis在启动时会扫描Mapper接口,为每个接口创建动态代理对象。当你调用userMapper.selectById(1)时,真正执行SQL的是MapperProxy。MyBatis打印日志时,默认用的类名是绑定SQL语句的那个Mapper接口的全限定名,而不是某个实现类。所以你在日志里看到的logger名称是com.example.project.mapper.UserMapper,而不是UserMapperImpl(MyBatis压根就没有实现类)。

这里就引出一个新人经常犯的错误:在logging.level里配了实现类的包名,比如com.example.project.mapper.impl: debug,结果什么都不打。真相是MyBatis日志的logger是Mapper接口本身,你应该配接口所在的包名或接口全限定名。如果你嫌一个包太大、日志太多,可以直接精确到某个接口:

logging: level: com.example.project.mapper.UserMapper: debug

3.3 用MyBatis Log Plugin插件还原完整SQL

日志能打出来了,但Preparing和Parameters是分开的,要复制给同事或者拿到生产环境排查还是不方便。这时候就轮到MyBatis Log Plugin这类插件登场了。

在IDEA的插件市场搜索MyBatis Log Plugin,安装后重启IDE,然后启动项目触发SQL执行,插件会单独开一个名为MyBatis Log的窗口面板,自动把MyBatis打在控制台的SQL和参数拼接成一条可以直接执行的语句,参数值会直接引号包裹或整型直出。复制出来就能跑,非常省事。

这个插件是免费且开源的,实测下来对绝大多数MyBatis项目都有效。它的原理是监听IDEA控制台的输出流,所以它能在不修改你项目代码的情况下工作——你甚至不需要特地为它调整日志级别,只要能看到PreparingParameters,它就能帮你拼好。

注意事项有两点。第一,由于它是靠解析控制台输出实现的,如果你的项目里Preparing日志被其他日志刷掉了,或者日志输出文件被日志框架二次改造过,插件可能拼不完整。这时候可以临时把logging.level.com.example.project.mapper调成trace,让日志更完整。第二,如果你装了多个类似插件(比如还有MyBatisX),两个插件可能会同时监听,面板里SQL重复显示是正常的,不用纠结。

3.4 没有插件时怎么手动拼出完整SQL

如果公司电脑不让装插件,或者你在服务器上排查问题,就只能手动拼SQL了。这个技能在我看来反而比插件更重要——它逼着你理解MyBatis的#{}${}差别。

-- MyBatis XML 中写的 SELECT * FROM user WHERE age > #{age} AND city = #{city}

这条SQL在Preparing阶段会被转成:

SELECT * FROM user WHERE age > ? AND city = ?

#{}在这里的作用是生成占位符?,对应的参数会在Parameters阶段依次列出来。手动拼的时候,你只需要把?按顺序替换成Parameters里的值。

但要注意字符串类型要加引号,比如Parameters里显示北京(String),替换过来就是'北京';而18(Integer)不需要加引号。如果你看到Parameters里有2024-01-01 10:00:00(Timestamp)这种日期类型,直接复制进SQL也是可以执行的,但部分数据库可能需要你加一个DATE('2024-01-01 10:00:00')来保证类型正确。

如果XML里写的是${city},那拼接规则完全不同。${}是字符串替换,MyBatis在构建SQL阶段就把变量直接拼接进SQL模板,日志里你会直接看到值,没有参数阶段。这种情况下要特别注意SQL注入风险——${}只建议用在表名、排序字段这种没法用占位符的地方,业务条件一律用#{}

手动拼完SQL之后,一般我都会粘到第2节说的IDEA数据库控制台里跑一遍,验证语法和执行结果。这样形成一个从日志到执行的完整闭环。

4. JPA/Hibernate场景:让Hibernate把话说完

MyBatis的SQL日志虽然拆成两段,至少信息是完整的。JPA/Hibernate这边情况更"隐蔽":你开了show-sql,它打出了一行Hibernate: select ...,但同样不打印参数。而且Hibernate生成的SQL通常还带了很多别名和嵌套子查询,可读性比MyBatis差不少。

4.1 show-sql开启后看到的是什么

先看最基础的配置:

spring: jpa: show-sql: true

这个配置的作用很简单,开启后Hibernate会把生成的SQL打印出来,默认走的是System.out,不经过日志框架。所以有时候你会看到控制台里SQL是杂乱的白色输出,完全不受logback的颜色和格式控制,原因就是它走的是标准输出而不是logger。

只配这一项,你看到的日志大概是这样的:

Hibernate: select u.id as id1_0_, u.name as name2_0_, u.age as age3_0_ from user u where u.age>=?

参数值藏在问号里,SQL很长又没有格式化。它能告诉你"大概执行了什么",但没法帮你直接复制去数据库验证。

补充一点,很多人以为spring.jpa.show-sql=true就够了,实际上它只是Hibernate的一个开关,而Hibernate通过System.out输出。如果你在日志配置文件里把标准输出禁用了,那SQL可能直接消失。更稳妥的做法是用下一小节的方式,让Hibernate的SQL也走日志框架。

4.2 format-sql与use_sql_comments带来的可读性提升

想让SQL可读性变好,可以追加两个属性:

spring: jpa: show-sql: true properties: hibernate: format_sql: true use_sql_comments: true

format_sql=true会把SQL格式化成一个字段一行的样子,嵌套子查询的缩进也清晰很多。use_sql_comments=true则会在SQL前面加一段注释,用来标明这条SQL是从哪个实体或哪个查询方法生成的。

比如开启后你会看到:

Hibernate: /* select com.example.entity.User */ select u.id as id1_0_, u.name as name2_0_, u.age as age3_0_ from user u where u.age>=?

虽然比原来好一点,但参数还是看不到。生产环境排查问题的时候,光知道SQL样子不知道参数值,等于白看。

4.3 把绑定参数一并打出来的关键配置

Hibernate把参数绑定信息打在名叫org.hibernate.type.descriptor.sql.BasicBinder的logger上,级别为TRACE。所以你要做的配置是:

logging: level: org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace

这样设置之后,日志会是这样的:

Hibernate: select u.id as id1_0_, u.name as name2_0_, u.age as age3_0_ from user u where u.age>=? binding parameter [1] as [INTEGER] - [18]

看到binding parameter这行,参数值就出来了。它和MyBatis的Parameters一样,告诉你第几个?绑定了什么值。

注意这里的logger路径和MyBatis的logger路径完全不同,别把MyBatis的Mapper包和Hibernate的logger混在一个配置里。在一个同时使用MyBatis和JPA的项目里,最终的日志配置通常长得像下面这样:

logging: level: com.example.project.mapper: debug org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace

两个体系各打各的,互不干扰。

还有一个小技巧:如果你用的是Hibernate 6.x(Spring Boot 3.x默认就是),BasicBinder的logger路径可能会因为包结构变化略有调整,建议在启动日志里搜一下binding parameter关键字,看看它实际是从哪个logger打出来的,然后再对应配置。时代不同,版本不同,配置可能会微调。

5. 数据源和监控层方案:Druid监控页与p6spy

日志在框架层解决的是"这条SQL是什么"的问题,而数据源和监控层解决的是"这个查询到底耗了多少时间、执行了多少次、有没有慢SQL"的问题。到排查性能问题的时候,这两类方案更好用。

5.1 Druid自带SQL监控面板

国内项目用Druid连接池非常普遍,它自带一个SQL监控功能。如果你的数据源是Druid,只需要开启StatFilter,再配置一个监控页面,就能通过浏览器看到所有SQL的执行统计。

核心配置大致如下:

spring: datasource: druid: filter: stat: enabled: true slow-sql-millis: 1000 stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: 123456

slow-sql-millis=1000表示执行超过1秒的SQL会被标记为慢SQL。启动项目后访问http://localhost:8080/druid/,输入账号密码,就能看到SQL监控面板。里面会列出每条SQL的执行次数、总耗时、最大耗时、返回行数等信息,还能按慢SQL筛选。我曾经靠它一次定位出一个线上接口每2秒卡一次的问题——把监控面板打开,看到某一类SQL执行时间波动很大,再点进去看执行计划和参数,很快就锁定了缓存失效的问题。

这个方案的优点是不用改业务代码,引入Druid后配置即生效。缺点是它监控的是SQL存储在数据库层面的聚合统计,对于"某一条具体请求执行了哪些SQL"这种问题帮助有限——它更多是给你一个全局视角,看哪些SQL最需要优化。

5.2 p6spy:一个被低估的SQL打印利器

p6spy是一个JDBC驱动级别的代理框架,它在你真正的数据库驱动外面包了一层,拦截所有经过JDBC的SQL语句,包括MyBatis、JPA、Spring JDBC,甚至手写JDBC,然后统一打印出来。

集成p6spy也很简单。引入依赖之后,配置spy.properties

appender=com.p6spy.engine.spy.appender.Slf4JLogger logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat customLogMessageFormat=%(executionTime)ms | %(sql)

然后在application.yml里把JDBC URL改掉:

spring: datasource: url: jdbc:p6spy:mysql://localhost:3306/demo?serverTimezone=UTC&useSSL=false driver-class-name: com.p6spy.engine.spy.P6SpyDriver

启动项目后,所有SQL会以12ms | SELECT * FROM user WHERE age > 18这种格式打印,时间、完整SQL一次到位,参数和模板提前帮你拼好了。这对于"想快速看到能直接复制的SQL"这个需求来说,体验比MyBatis Log Plugin还好,因为它在驱动层就把活干完了,跟ORM框架无关,JPA、MyBatis都能覆盖。

不过要提两个坑。第一,p6spy包在JDBC驱动外层,意味着你拿到的Connection被代理了,连接池测试、服务降级等场景可能会受到影响,生产环境需要充分测试后再决定开不开。第二,p6spy会把所有的SQL都打出来,包括某些框架内部生成的查询元数据的SQL,日志量会比框架日志方案大不少,所以线上环境建议只在调试窗口期短暂开启,平时关闭。

5.3 两种方案怎么选

很多人会在Druid监控和p6spy之间犹豫,我的选择标准很简单:

表格对比一下:

维度Druid监控面板p6spy
能否看到完整SQL能看到具体的SQL文本能看到,且带执行耗时
参数是否拼接好不拼接直接拼接好
统计SQL执行次数支持,有聚合统计不支持聚合,只能逐条看
慢SQL分析支持,可配置慢SQL阈值不支持直接标慢SQL,要靠日志分析
对生产的影响低,已是常用连接池中,代理JDBC有额外开销
适合场景长期监控、慢SQL治理开发调试、联调排查、短时间定位问题

如果你要长期观察数据库的压力,用Druid监控面板;如果你就是想在开发和联调阶段快速看清SQL、复制SQL、验证SQL,用p6spy或者第3节说的MyBatis Log Plugin就够了。两个方案不是互相排斥的,我就见过项目里开发环境开p6spy,生产环境靠Druid监控面板兜底,两边各司其职。

还有一个折中的做法:如果你不想动JDBC URL,也可以直接用log4jdbc配合Spring Boot的DataSource代理。但log4jdbc在国内社区维护得不如p6spy活跃,配置起来更绕,所以除非你本身就在用log4jdbc,否则新增项目我建议直接上p6spy更省心。

写在最后

现在回头看整个"IDEA输出SQL"的链条,其实每一条路都有它明确的适用边界。我自己的习惯是:开发环境装一个MyBatis Log Plugin,遇到JPA项目就靠show-sql + BasicBinder trace,需要长期关注SQL性能的时候开Druid监控面板,真要快速定位现场问题就直接用IDEA自带的数据库控制台跑一遍。

这里面还有一个很容易被忽略的实际经验:在公司里排查问题,经常是拿到一个线上报错,只有SQL文本却没有参数值。这时候如果你能用第3.4节的方法手动把参数拼回去,再连到测试环境数据库跑一遍,很多问题就能当场复现和验证。这个手工能力,比任何插件都靠得住。希望这篇写下来,能帮你省下那些在控制台和数据库客户端之间来回粘贴、反复替换参数的时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 1:13:28

Agent技能层:决定LLM应用上限的关键工程实践

Agent技能层&#xff0c;才是决定LLM应用上限的关键做Agent开发这两年多&#xff0c;我最大的感触是&#xff1a;模型选型固然重要&#xff0c;但真正决定一个Agent能干什么、干得稳不稳的&#xff0c;其实是中间那层技能层&#xff08;agent-skills&#xff09;的工程设计。同…

作者头像 李华
网站建设 2026/9/19 1:13:26

Zabbix 7.4 + Rocky Linux 9.6 SNMP监控全链路实战指南

1. 项目概述&#xff1a;Zabbix如何真正“看懂”设备——从被动接收走向主动理解SNMP数据Zabbix获取客户端的SNMP数据&#xff0c;不是简单地把一个IP填进监控界面就完事。它是一整套设备语言翻译系统&#xff1a;Zabbix是那个坐在监控室里的资深工程师&#xff0c;SNMP是设备厂…

作者头像 李华
网站建设 2026/9/19 1:13:23

Git命令行与GUI双线实战:安装配置、分支管理与排错

1. 命令行与图形界面到底该选谁&#xff1a;先搞清使用场景刚上手版本控制的人&#xff0c;几乎都会在同一个岔路口停下来&#xff1a;一边是黑底白字的终端&#xff0c;git status、git commit敲下去&#xff0c;输出一屏信息看得心里发虚&#xff1b;另一边是各种图形客户端&…

作者头像 李华
网站建设 2026/9/19 5:15:14

STM32智能鱼缸:本地闭环控制与微信小程序物联网设计

简介&#xff1a;这是一份面向嵌入式物联网学习者及课程设计、毕业设计需求者的项目文档&#xff0c;围绕基于STM32的智能鱼缸系统与配套微信小程序展开。资源以单个PDF交付&#xff0c;压缩包约42.7MB&#xff0c;正文系统梳理了以STM32F103RCT6为主控的硬件方案&#xff0c;涵…

作者头像 李华
网站建设 2026/9/19 5:15:57

PHP与Python:Web开发语言对比与技术选型指南

1. 语言背景与定位差异PHP和Python作为两种主流的服务器端编程语言&#xff0c;各自有着截然不同的发展轨迹和应用场景。PHP最初由Rasmus Lerdorf于1994年创建&#xff0c;设计初衷是为了管理个人主页&#xff08;Personal Home Page&#xff09;&#xff0c;后来逐渐演变成专业…

作者头像 李华
网站建设 2026/9/19 5:24:17

桌面端 Coding Agent:多模型、Subagent 与 MCP 配置实战

1. 从 Claude Code 说起&#xff1a;为什么桌面端 Coding Agent 是个真需求Claude Code 火了一整年&#xff0c;命令行里敲claude然后看着它读文件、改代码、跑测试&#xff0c;确实爽。但用久了你会发现一个问题&#xff1a;它是个 CLI 工具&#xff0c;本质上是"寄生&qu…

作者头像 李华