整理常见的Mybaits打印执行sql和耗时时间的方案

Mybaits内部封装了JDBC,这样程序开发人员只需要关注sql本身的业务开发从而减少了开发者的开发复杂性, Mybaits 作为一款优化的持久化框架得到各大公司的青睐。如何输出Mybatis运行中执行sql和耗时时间呢?今天我们来介绍几种常见的方案。

1、配置文件方案

在配置文件中配置打印sql的日志,这样每当运行的数据库sql的时候都会在指定的位置(如控制台中)输出当前执行的sql语句、sql中的参数等信息,如下是配置打印sql的配置信息:

spring: 
  application: 
    name: longxia-biancheng 
#数据库的配置 
  datasource: 
    username: longxia 
    password: longxiabiancheng 
    driver-class-name: com.mysql.jdbc.Driver 
    url: jdbc:mysql://localhost:3306/longxia?rewriteBatchedStatements=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true 
# mybatis的实体类和mapper.xml文件的位置信息 
mybatis: 
  type-aliases-package: com.longxia.mybatis.entity 
  mapper-locations: classpath:mapper/*.xml 
#执行包下的日志级别(输出执行sql) 
logging: 
  level: 
    com.longxia.mybatis.mapper: debug

效果如下所示:

执行的sql的时间我们可以通过打印的日志的时间做一个预估,这种方式打印sql只需要做简单的配置即可,往往测试和预发上寻找问题而启用的,线上不建议使用。

2、Mybatis插件实现方案

在Mybatis中提供了四大核心的插件分别是: ParameterHandler 、 ResultSetHandler 、 StatementHandler 、Executor。下列罗列了各个接口的作用:

接口 作用
Executor 在执行具体的数据库操作之前,通过它来查询一级缓存和二级缓存,所以操作数据库的第一步需要执行 Executor
StatementHandler 在Executor执行完毕之后,通过它执行具体的数据库操作
ParameterHandler 在执行sql语句之前,可以设置参数(如修改参数值,过滤敏感数据等)
ResultSetHandler 可以修改查询出来的结果

实现的原理:Mybatis使用JDK的动态代理来为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法。

因为我们获取sql和sql执行的时间,所以我们只需要使用StatementHandler接口即可,根据实现的原理以查询为案例编写的代码如下所示:

@Component 
@Intercepts({@Signature(    
        //这里的类只能是mybaits底层指定的四大核心类 
        type = StatementHandler.class, 
        //增强的方法 
        method= "query", 
        //指定方法的参数 
        args = {Statement.class, ResultHandler.class})}) 
public class LongxiaSqlLogInterceptor implements Interceptor { 
    /** 
     * 实现具体的功能 
     * 
     */ 
    @Override 
    public Object intercept(Invocation invocation) throws Throwable { 
        long startTime = System.currentTimeMillis(); 
        //获取增加的对象 
        StatementHandler target = (StatementHandler) invocation.getTarget(); 
        //获取sql 
        String sql = target.getBoundSql().getSql(); 
        //放行继续执行数据库操作 
        Object proceed = invocation.proceed(); 
        //打印日志或者超时告警 
        System.out.println("当前的sql:" + sql + ", 消耗时间:" + (System.currentTimeMillis() - startTime)); 
        return proceed; 
    } 
    @Override 
    public Object plugin(Object target) { 
        return Plugin.wrap(target, this); 
    } 
    @Override 
    public void setProperties(Properties properties) { 
    } 
}

效果如下所示:

通过插件的方式可以打印sql语句和获取执行时间,我们可以和轻松的就实现一套如果sql超过设定的时间,那么输出sql和执行时间等关键信息发送到公司的技术监控群中。

3、第三方框架—— p6spy

p6spy可以实现在不必对业务代码做任何修改的情况下对数据库操作无缝衔接,可以更直观的展示数据的执行的sql和sql消耗的时间,下面是接入 p6s py的流程:

(1)添加依赖

<dependency> 
     <groupId>p6spy</groupId> 
     <artifactId>p6spy</artifactId> 
     <version>3.8.7</version> 
</dependency>

(2)配置数据库

spring: 
#数据库的配置 
  datasource: 
    username: longxia 
    password: longxiabiancheng 
#数据库驱动修改 
    #driver-class-name: com.mysql.jdbc.Driver 
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver 
#数据库连接上添加p6spy 
    url: jdbc:p6spy:mysql://localhost:3306/longxia?rewriteBatchedStatements=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true

(3)添加 p6s py的配置文件(resource下添加一个spy.properties)

#真实的数据库连接 
realdriver=com.mysql.jdbc.Driver 
# 单行日志 
logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat 
# 使用Slf4J记录sql 
appender=com.p6spy.engine.spy.appender.Slf4JLogger 
# 是否开启慢SQL记录 
#outagedetection=true 
# 慢SQL记录标准,单位秒 
#outagedetectioninterval=2 
#日期格式 
dateformat=yyyy-MM-dd HH:mm:ss

运行的效果:

2024-07-13 17:37:31.068  INFO 29615 --- [nio-8080-exec-1] p6spy :  
-07-13 17:37:31|31|statement|connection 0| 
url jdbc:p6spy:mysql://localhost:3306/longxia?rewriteBatchedStatements=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true| 
select * from student where id = ?| 
select * from student where id = 1

至此完成了接入p6spy打印sql语句和统计耗时时间的信息输出,但是依然建议在测试和预发环境使用,如果线上使用就打印太多这样的日志会影响系统性能。

3