JDBC使用p6spy记录实际执行SQL方法【解决SQL打印两次问题】

p6spy介绍

p6spy 是一个开源的 JDBC 数据源代理工具,主要用于拦截和记录应用程序与数据库之间的所有 SQL 操作,方便开发者进行 SQL 调试、性能监控和问题排查。

p6spy可以打印实际执行的sql,在开发过程中方便调试,和使用框架无关,Mybatis和Hibernate等都可以使用。

使用方法

1.引入p6spy依赖
xml 复制代码
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.9.1</version>
        </dependency>
2.修改数据库连接信息,这里我使用的是Druid连接池
java 复制代码
      oracleDataSource = new DruidDataSource();
      // 替换jdbcUrl为p6spy代理格式
//        oracleDataSource.setUrl("jdbc:oracle:thin:@10.5.1.22:1521/ORCL");
      oracleDataSource.setUrl("jdbc:p6spy:oracle:thin:@10.5.1.22:1521/ORCL");
      oracleDataSource.setUsername("ORACLE_USERNAME");
      oracleDataSource.setPassword("ORACLE_PASSWORD");
      // 指定p6spy的代理类
      oracleDataSource.setDriverClassName("com.p6spy.engine.spy.P6SpyDriver");
3.在resource目录下创建配置文件spy.properties
复制代码
# 指定日志实现,这里使用 SLF4J 输出日志
appender=com.p6spy.engine.spy.appender.Slf4JLogger

# 配置日志格式处理器,默认为SingleLineFormat是单行输出,这里指定多行日志输出格式,增加可读性
logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
4. 控制台成功打印日志
shell 复制代码
10:58:53.784 [main] INFO p6spy - #1732071533784 | took 1ms | statement | connection 4| url jdbc:p6spy:oracle:thin:@10.5.1.22:1521/ORCL
SELECT 1 FROM DUAL
SELECT 1 FROM DUAL;
10:58:53.798 [main] INFO p6spy - #1732071533798 | took 12ms | statement | connection 4| url jdbc:p6spy:oracle:thin:@10.5.1.22:1521/ORCL
SELECT * FROM cms_mid_table WHERE table_module='CIF'
SELECT * FROM cms_mid_table WHERE table_module='CIF';

SQL打印两遍

原因

查看日志格式处理器代码,很简单,就是将参数组装成打印结果

java 复制代码
public class MultiLineFormat implements MessageFormattingStrategy {
    @Override
    public String formatMessage(final int connectionId, final String now, final long elapsed, final String category, final String prepared, final String sql, final String url) {
      return "#" + now + " | took " + elapsed + "ms | " + category + " | connection " + connectionId + "| url " + url + "\n" + prepared + "\n" + sql +";";
    }
}

我在代码中是使用apache工具类QueryRunner#excute直接执行sql,没有使用预编译参数

java 复制代码
    public void test()  {
        QueryRunner queryRunner = new QueryRunner();
        try {
            queryRunner.execute("DELETE FROM cms_mid_table WHERE table_module='CIF'", ETLConnectionPool.getObConnection());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

QueryRunner内部使用的是PrepareStatement的子类CallableStatement

java 复制代码
    private int execute(Connection conn, boolean closeConn, String sql, Object... params) throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        }
        if (sql == null) {
            if (closeConn) {
                close(conn);
            }
            throw new SQLException("Null SQL statement");
        }
        CallableStatement stmt = null;
        int rows = 0;
        try {
            stmt = this.prepareCall(conn, sql);
            this.fillStatement(stmt, params);
            stmt.execute();
            rows = stmt.getUpdateCount();
            this.retrieveOutParameters(stmt, params);
        } catch (SQLException e) {
            this.rethrow(e, sql, params);
        } finally {
            close(stmt);
            if (closeConn) {
                close(conn);
            }
        }
        return rows;
    }

因为我是直接执行的具体的SQL,所以预编译对象和实际直接的sql相同,导致打印结果看上去是相同的SQL打印了两遍。

解决方法

  1. 创建自定义日志格式化类
java 复制代码
@Slf4j
public class CustomLogFormatter implements MessageFormattingStrategy {
  @Override
  public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
  	// 自定义sql日志内容
      return "#" + now + " | took " + elapsed + "ms | connection " + connectionId + "| " + url + "\n"
              + (sql.startsWith("\n") ? "" : "\n") + sql.replaceAll(" {2,}", " ") + (sql.endsWith(";") ? "" : ";")
              + "\n--------------------------------------------------------------------------------------------\n";
  }
}
  1. 在spy.properties配置文件中指定自定义格式类

    使用 SLF4J 输出日志

    appender=com.p6spy.engine.spy.appender.Slf4JLogger

    使用自定义日志格式处理器

    logMessageFormat=com.dhcc.utils.CustomLogFormatter
    #logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
    #logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat

相关推荐
Lucifer三思而后行1 小时前
最近折腾了一下金仓数据库的备份恢复
数据库·备份恢复
草莓熊Lotso2 小时前
【C++ 基于微服务的即时通讯系统】文件存储子服务实战:UUID生成、文件IO封装与全接口单元测试
开发语言·网络·数据库·c++·微服务·架构·单元测试
进击的荆棘3 小时前
MySQL——库的操作
数据库·mysql
2601_967980114 小时前
“小程序·云开发”上线 助力开发者的高效利器
数据库·存储管理·小程序云开发·云函数·开发者工具
zhangjw344 小时前
1-9 SELECT 查询基础:where、group by、having、order by、limit offset
数据库·postgresql·pg数据库
唐老板4 小时前
Postgres 慢查询,先取证再动手
数据库·ai编程
笃行35011 小时前
数据库性能优化的五处内核改造,KingbaseES 配了四件诊断工具
数据库
非优秀程序员13 小时前
第02章 杭电的英语老师与一台服务器|《互联网产品恩仇录》
数据库·node.js·代码规范
SelectDB18 小时前
SelectDB 是哪个公司的?是国产数据库吗?一篇讲清飞轮科技与 Apache Doris 的关系
数据库
这个DBA有点耶18 小时前
库内计算推理:一条SQL背后的算子融合,和外挂方案差了几次网络往返
数据库·人工智能·sql