Mybatis调试经常需要输出SQL语句,但你知道是怎么输出的嘛?

Java 开发中常用的几款日志框架有很多种,并且这些日志框架来源于不同的开源组织,给用户暴露的接口也有很多不同之处,所以很多开源框架会自己定义一套统一的日志接口,兼容上述第三方日志框架,供上层使用。

一般实现的方式是使用适配器模式,将各个第三方日志框架接口转换为框架内部自定义的日志接口。MyBatis 也提供了类似的实现,这里我们就来简单了解一下。

适配器模式是什么?

简单来说,适配器模式主要解决的是由于接口不能兼容而导致类无法使用的问题,这在处理遗留代码以及集成第三方框架的时候用得比较多。其核心原理是:通过组合的方式,将需要适配的类转换成使用者能够使用的接口。

日志???/h1>

MyBatis 自定义的 Log 接口位于 org.apache.ibatis.logging 包中,相关的适配器也位于该包中。 首先是 LogFactory 工厂类,它负责创建 Log 对象,在 LogFactory 类中有一段静态代码块,其中会依次加载各个第三方日志框架的适配器。

static {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
}

以 JDK Logging 的加载流程(useJdkLogging() 方法)为例,其具体代码实现和注释如下:

/**
 * 首先会检测 logConstructor 字段是否为空,
 * 1.如果不为空,则表示已经成功确定当前使用的日志框架,直接返回;
 * 2.如果为空,则在当前线程中执行传入的 Runnable.run() 方法,尝试确定当前使用的日志框架
 */
private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
        try {
            runnable.run();
        } catch (Throwable t) {
            // ignore
        }
    }
}

public static synchronized void useJdkLogging() {
    setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}

private static void setImplementation(Class<? extends Log> implClass) {
    try {
        // 获取适配器的构造方法
        Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
        // 尝试加载适配器,加载失败会抛出异常
        Log log = candidate.newInstance(LogFactory.class.getName());
        // 加载成功,则更新logConstructor字段,记录适配器的构造方法
        logConstructor = candidate;
    } catch (Throwable t) {
        throw new LogException("Error setting Log implementation.  Cause: " + t, t);
    }
}

打印SQL语句

如何开启打印
这里演示Mybatis在运行时怎么输出SQL语句,具体分析见原理章节。

单独使用Mybatis

在mybatis.xml配置文件中添加如下配置:

<setting name="logImpl" value="STDOUT_LOGGING" />

和SpringBoot整合

有两种方式,第一种也是利用StdOutImpl实现类去实现打印,在application.yml配置文件填写如下:

#mybatis配置
mybatis:
  # 控制台打印sql日志
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

其次我们还可以通过指定日志级别来输出SQL语句:

SpringBoot默认使用的SL4J(日志门面)+Logback(具体实现)的日志组合

logging:
  level:
    xx包名: debug

简单分析原理

这里我们直接看到
org.apache.ibatis.executor.BaseExecutor#getConnection方法,了解Mybatis的应该都知道Mybatis在执行sql操作的时候会去获取数据库连接

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    // 判断日志级别是否为Debug,是的话返回代理对象
    if (statementLog.isDebugEnabled()) {
        return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
        return connection;
    }
}

可以看到我注释的那行,它通过判断日志级别来判断是否返回ConnectionLogger代理对象,那么我们前面提到 Log 接口的实现类中StdOutImpl它的isDebugEnabled其实是永远返回 true,代码如下:

并且它直接用的 System.println去输出的SQL信息

public class StdOutImpl implements Log {
    
  // ...省略无关代码
      
  @Override
  public boolean isDebugEnabled() {
    return true;
  }

  @Override
  public boolean isTraceEnabled() {
    return true;
  }

  @Override
  public void error(String s, Throwable e) {
    System.err.println(s);
    e.printStackTrace(System.err);
  }

  @Override
  public void error(String s) {
    System.err.println(s);
  }
  // ...省略无关代码
}

到这里起码你知道了为什么我们通过配置 MyBatis 所用日志的具体实现 logImpl就可以实现日志输出到控制台的效果了。

那么我们还可以深究一下 statementLog 是在什么时候变成 StdOutImpl的,在解析Mybatis配置文件的时候,会去读取我们配置的logImpl属性,然后通过
LogFactory.useCustomLogging方法先指定好适配器的构造方法

// org.apache.ibatis.builder.xml.XMLConfigBuilder#loadCustomLogImpl  
private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
}

public void setLogImpl(Class<? extends Log> logImpl) {
    if (logImpl != null) {
        this.logImpl = logImpl;
        LogFactory.useCustomLogging(this.logImpl);
    }
}

然后在构建MappedStatement的时候就已经将日志对象初始化好了

每个MappedStatement对应了我们自定义Mapper接口中的一个方法,它保存了开发人员编写的SQL语句、参数结构、返回值结构、Mybatis对它的处理方式的配置等细节要素,是对一个SQL命令是什么、执行方式的完整定义。

public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
    // ...省略无关代码
    mappedStatement.statementLog = LogFactory.getLog(logId);
    mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}

public static Log getLog(String logger) {
    try {
        return logConstructor.newInstance(logger);
    } catch (Throwable t) {
        throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
    }
}

最后SpringBoot的就不概述了

  • 第一种方式其实也是同理
  • 第二种方式是通过修改了日志级别,然后使 isDebugEnabled 返回true,去返回代理对象,然后去输出SQL语句。

感兴趣的还可以看看SQL语句的输出是怎么输出的,具体在 ConnectionLogger的invoke方法中,你会发现熟悉的 Preparing: "和"Parameters: "。

完结撒花,看完了点个赞呗~。

作者:Linn
链接:
https://juejin.cn/post/7039136891731443743

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容