告别SQL盲猜!6种方案带你玩转SQL打印

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

在我们日常开发中,SQL的打印我们不太关注。因为线下的调试,我们通过断点,日志等方式可以快速定位问题。

但是提测之后或者线上,发现数据与预期的数据不符,需要对比SQL的条件。线上的不能随意发布代码,导致排查问题变的困难。

本期,我们从线下到线上整理一下几种好用的打印SQL的方案:

  • ORM内置日志功能
  • P6Spy框架
  • 日志框架
  • Mybaits拦截器
  • AOP切面
  • Arthas打印SQL

02 ORM内置日志功能

Mybaits是一款非常优秀的ORM框架,DAO层的设计它占了半壁江山。而其内部配置本身支持SQL的打印,线下测试的时候,我们经常会将次配置打开,以便随时了解SQL的执行情况。

properties配置

properties 复制代码
# 控制台输出SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

xml配置

mybatis-configuration.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<setting name="logImpl" value="STDOUT_LOGGING"/>
	</settings>
</configuration>

properties配置和xml配置,任意配置一项即可。

结果

打印的参数和结果都有了,这样的SQL已经足够我们排查问题。但是不太方便复制值别人查看,需要替换参数才行。Idea的插件Mybatis log free可以帮我们自动填充参数,用起来简直不要太爽。

03 P6Spy 实现

GitHub地址:github.com/p6spy/p6spy

这是一款第三方的数据库日志框架,里面提供了很多配置,可以随意定制日志格式。配置详见:

p6spy.readthedocs.io/en/latest/c...

3.1 Maven依赖

xml 复制代码
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>${latest.version}</version>
</dependency>

3.2 配置修改

properties 复制代码
# 数据源
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:p6spy:mysql://127.0.0.1:3306/test
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test

需要修改源数据源的参数,具有代码侵入性。

3.3 创建 spy.properties

properties 复制代码
# 部分配置
module.log=com.p6spy.engine.logging.P6LogFactory
appender=com.p6spy.engine.spy.appender.StdoutLogger
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(executionTime)ms | SQL: %(sqlSingleLine)

3.4 结果

结果可以看到数据库连接的信息,预执行的SQL以及执行的SQL

04 日志框架

日志框架的方式,是可以指定任意包的。

配置

properties 复制代码
# logging.level.xxxx=debug
logging.level.com.simonking.boot.mybaits.mapper.UserInfoNativeMapper=debug

这里指定我们需要打印的Mapper

结果

这种方式也是我们生产上经常使用的动态监控某个包下SQL的方案。此方案需要借助配置中心apollonacos等,动态更新日志的级别,已达到按需打印SQL的目的。

05 Mybaits拦截器

Mybaits拦截器,之前的文章里介绍过,就不再赘述。我们需要拦截的是org.apache.ibatis.executor.statement.StatementHandlerprepare()方法。

java 复制代码
@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class})
})
@Component
public class SqlPrintInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        // 获取原始SQL
        String sql = boundSql.getSql();

        Object param = boundSql.getParameterObject();

        // 美化SQL输出
        System.out.println("\n=============== SQL日志 ===============");
        System.out.println("原始SQL:" + sql);
        System.out.println("SQL: " + sql.replaceAll("\\s+", " ").trim());
        System.out.println("参数: " + JSON.toJSONString(param));
        System.out.println("=====================================");

        return invocation.proceed();
    }
}

结果

06 AOP动态拦截

AOP的做法,需要熟悉调用SQL的方法。实现起来门槛较高,需要了解具体的参数。

我们这里拦截Mapper,和日志框架类似。

java 复制代码
@Aspect
@Component
public class SqlLoggerAspect {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Around("execution(* com.simonking.boot.mybaits.mapper.UserInfoNativeMapper.*(..))")
    public Object logSql(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature)pjp.getSignature();
        Method method = signature.getMethod();
        String namespace = method.getDeclaringClass().getName();
        String methodName = method.getName();

        Configuration configuration = sqlSessionFactory.getConfiguration();
        MappedStatement mappedStatement = configuration.getMappedStatement(namespace + "." + methodName);

        Map<String, Object> map = new HashMap<>();
        Object obj = pjp.getArgs()[0];
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = field.get(obj);
            map.put(fieldName, value);
        }

        BoundSql boundSql = mappedStatement.getBoundSql(map);

        System.out.println("[AOP SQL] " + boundSql.getSql());
        System.out.println("[AOP SQL 参数]: " + JSON.toJSONString(map));
        return pjp.proceed();
    }
}

结果

07 Arthas打印SQL

Arthas是一款Java在线诊断工具,之前也介绍过。用它也可以打印SQL。

sh 复制代码
# 全部参数
watch org.apache.ibatis.executor.SimpleExecutor doQuery '{params[0].resource,params[0].id,params[4].sql,params[4].parameterObject,returnObj,throwExp}' 'params[0].id.contains("xxx.xxxMapper")' -x 3

# 只打印sql和参数
watch org.apache.ibatis.executor.SimpleExecutor doQuery '{params[4].sql,returnObj}' 'params[0].id.contains("xxx.xxxMapper")' -x 3

结果

这种方式在生产上应用也非常使用,根据需要抓取需要的SQL信息。

但是这样的方法是对SQL的注解脚本无效,如:@Select@Update

08 小结

线下的SQL打印可以随意玩,怎么方便怎么来。但是线上的打印SQL的方案就需要谨慎。如果开启打印SQL,那么频繁打印SQL不仅存在数据安全问题,还会引起CPU标高或者线程池变满(我已经被毒打过了,不要轻易去试)。

线上还是推荐按需收集SQL,推荐采用配置中心实时修改日志级别,或者Arthas按需采集SQL

相关推荐
redreamSo5 分钟前
AI Daily | AI日报:哈萨比斯:AI能建模所有进化事物; Anthropic 反杀 OpenAI,称霸企业 LLM 市场; 马斯克与LeCun激辩:研究者是否存在?
程序员·aigc·资讯
爱编程的鱼10 分钟前
计算机(电脑)是什么?零基础硬件软件详解
java·开发语言·算法·c#·电脑·集合
求知若渴,虚心若愚。15 分钟前
ansible.cfg 配置文件生成
java·服务器·ansible
雪域迷影17 分钟前
使用AssemblyAI将音频数据转换成文本
java·音视频·restapi·gson·assemblyai
CF14年老兵36 分钟前
2025 年每个开发人员都应该知道的 6 个 VS Code AI 工具
前端·后端·trae
think12337 分钟前
带你走进Spring Cloud的世界
spring boot·后端·spring cloud
没逻辑1 小时前
Goroutine 死锁定位与调试全流程
后端
IH_LZH1 小时前
kotlin小记(1)
android·java·前端·kotlin
无限大61 小时前
Java 随机数生成:从青铜到王者的骚操作指南
后端·程序员
二闹1 小时前
后端开发:这5个技巧让你少写一半代码!
java·后端·project lombok