Mybatis基础模块-日志管理

文章目录

  • 1. 适配器模式
  • 2. Log
    • 2.1 默认实现StdOutImpl
    • 2.2 Log4jImpl
  • 3. LogFactory
  • 4. 解析配置和应用
    • 4.1 settings配置
    • 4.2 解析
  • 5. jdbc日志
    • 5. 1 类图
    • 5.2 BaseJdbcLogger
    • 5.3 ConnectionLogger
    • 5.4 ConnectionLogger的具体应用

1. 适配器模式

适配器使接口不兼容的对象可以相互合作。
在这里插入图片描述
Java的日志框架有很多,Log4j,Log4j2,Apache Commons Log,java.util.logging,slf4j等,接口不尽相同,Mybatis为了统一匹配这些框架,使用到了适配器模式。

2. Log

Mybatis自定义了日志接口:org.apache.ibatis.logging.Log。

/*** @author Clinton Begin*/
public interface Log {boolean isDebugEnabled();boolean isTraceEnabled();void error(String s, Throwable e);void error(String s);void debug(String s);void trace(String s);void warn(String s);}

以下为其类图:
在这里插入图片描述
上图的实现相当于adapter。

2.1 默认实现StdOutImpl

public class StdOutImpl implements Log {public StdOutImpl(String clazz) {// Do Nothing}@Overridepublic boolean isDebugEnabled() {return true;}@Overridepublic boolean isTraceEnabled() {return true;}@Overridepublic void error(String s, Throwable e) {System.err.println(s);e.printStackTrace(System.err);}@Overridepublic void error(String s) {System.err.println(s);}@Overridepublic void debug(String s) {System.out.println(s);}@Overridepublic void trace(String s) {System.out.println(s);}@Overridepublic void warn(String s) {System.out.println(s);}
}

这个实现很简单,就是控制台把日志打印出来。

2.2 Log4jImpl

为了匹配Log4j,该类持有org.apache.log4j.Logger,Logger就是上图adaptee,被匹配的接口。

/*** @author Eduardo Macarron*/
public class Log4jImpl implements Log {private static final String FQCN = Log4jImpl.class.getName();private final Logger log;public Log4jImpl(String clazz) {log = Logger.getLogger(clazz);}@Overridepublic boolean isDebugEnabled() {return log.isDebugEnabled();}@Overridepublic boolean isTraceEnabled() {return log.isTraceEnabled();}@Overridepublic void error(String s, Throwable e) {log.log(FQCN, Level.ERROR, s, e);}@Overridepublic void error(String s) {log.log(FQCN, Level.ERROR, s, null);}@Overridepublic void debug(String s) {log.log(FQCN, Level.DEBUG, s, null);}@Overridepublic void trace(String s) {log.log(FQCN, Level.TRACE, s, null);}@Overridepublic void warn(String s) {log.log(FQCN, Level.WARN, s, null);}}

3. LogFactory

LogFactory使用工厂模式,创建各类适配器。 该类用final修饰,不可继承,同时构造方法是私有的,不能通过new方法创建,像是一个工具类。


/*** @author Clinton Begin* @author Eduardo Macarron*/
public final class LogFactory {/*** Marker to be used by logging implementations that support markers.*/public static final String MARKER = "MYBATIS";private static Constructor<? extends Log> logConstructor;static {// 按序加载对应的日志组件,从上往下加载,上面的成功了,下面的就不会在加载了/*** tryImplementation(LogFactory::useSlf4jLogging); 等价于* tryImplementation(new Runnable(){*   void run(){*     useSlf4jLogging();*   }* })*/tryImplementation(LogFactory::useSlf4jLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);}private LogFactory() {// disable construction}public static Log getLog(Class<?> aClass) {return getLog(aClass.getName());}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);}}public static synchronized void useCustomLogging(Class<? extends Log> clazz) {setImplementation(clazz);}public static synchronized void useSlf4jLogging() {setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);}public static synchronized void useCommonsLogging() {setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);}public static synchronized void useLog4JLogging() {setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);}public static synchronized void useLog4J2Logging() {setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);}public static synchronized void useJdkLogging() {setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);}public static synchronized void useStdOutLogging() {setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);}public static synchronized void useNoLogging() {setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);}private static void tryImplementation(Runnable runnable) {if (logConstructor == null) {try {runnable.run();} catch (Throwable t) {// ignore}}}private static void setImplementation(Class<? extends Log> implClass) {try {// 获取指定适配器的构造方法Constructor<? extends Log> candidate = implClass.getConstructor(String.class);// 实例化适配器Log log = candidate.newInstance(LogFactory.class.getName());if (log.isDebugEnabled()) {log.debug("Logging initialized using '" + implClass + "' adapter.");}// 初始化 logConstructor 字段logConstructor = candidate;} catch (Throwable t) {throw new LogException("Error setting Log implementation.  Cause: " + t, t);}}}

4. 解析配置和应用

问题:mybatis是如何选择日志框架的。

4.1 settings配置

在这里插入图片描述
配置值包括:SLF4J , LOG4J(deprecated since 3.5.9) , LOG4J2 , JDK_LOGGING |,COMMONS_LOGGING , STDOUT_LOGGING , NO_LOGGING。

配置的值是如何来的?
在配置对象Configuration的构造方法里面。
在这里插入图片描述

4.2 解析

XMLConfigBuilder的方法:loadCustomLogImpl().

private void loadCustomLogImpl(Properties props) {// 获取 logImpl设置的 日志 类型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);}}

继续:

public static synchronized void useCustomLogging(Class<? extends Log> clazz) {setImplementation(clazz);}

继续:

private static void setImplementation(Class<? extends Log> implClass) {try {// 获取指定适配器的构造方法Constructor<? extends Log> candidate = implClass.getConstructor(String.class);// 实例化适配器Log log = candidate.newInstance(LogFactory.class.getName());if (log.isDebugEnabled()) {log.debug("Logging initialized using '" + implClass + "' adapter.");}// 初始化 logConstructor 字段logConstructor = candidate;} catch (Throwable t) {throw new LogException("Error setting Log implementation.  Cause: " + t, t);}}

5. jdbc日志

有了以上的类,那mybatis是如何打印日志的?

Mybatis通过JDK动态代理的方式,将JDBC操作通过指定的日志框架打印出来。

5. 1 类图

BaseJdbcLogger

5.2 BaseJdbcLogger

BaseJdbcLogger是一个基础的抽象类。基本的属性:

// 记录 PreparedStatement 接口中定义的常用的set*() 方法protected static final Set<String> SET_METHODS;// 记录了 Statement 接口和 PreparedStatement 接口中与执行SQL语句有关的方法protected static final Set<String> EXECUTE_METHODS = new HashSet<>();// 记录了PreparedStatement.set*() 方法设置的键值对private final Map<Object, Object> columnMap = new HashMap<>();// 记录了PreparedStatement.set*() 方法设置的键 keyprivate final List<Object> columnNames = new ArrayList<>();// 记录了PreparedStatement.set*() 方法设置的值 Valueprivate final List<Object> columnValues = new ArrayList<>();protected final Log statementLog;// 用于日志输出的Log对象protected final int queryStack;  // 记录了SQL的层数,用于格式化输出SQL

5.3 ConnectionLogger


/*** Connection proxy to add logging.** @author Clinton Begin* @author Eduardo Macarron**/
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {// 真正的Connection对象private final Connection connection;private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {super(statementLog, queryStack);this.connection = conn;}/*** Connection 是一个数据库连接对象*            通过 Connection 的下一步是创建 Statement 对象*            Statement包含对应的子类 PreparedStatement**            日志记录  Connection 创建 Statement 的过程*            同时会创建 Statement 的代理对象类增强 Statement** @param proxy* @param method* @param params* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] params)throws Throwable {try {// 如果是调用从Object继承过来的方法,就直接调用 toString,hashCode,equals等if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, params);}// 如果调用的是 prepareStatement方法if ("prepareStatement".equals(method.getName())) {if (isDebugEnabled()) {debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);}// 创建  PreparedStatementPreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);// 然后创建 PreparedStatement 的代理对象 增强stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);return stmt;// 同上} else if ("prepareCall".equals(method.getName())) {if (isDebugEnabled()) {debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);}PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);return stmt;// 同上} else if ("createStatement".equals(method.getName())) {Statement stmt = (Statement) method.invoke(connection, params);stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);return stmt;} else {return method.invoke(connection, params);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}/*** Creates a logging version of a connection.** @param conn - the original connection* @return - the connection with logging*/public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);ClassLoader cl = Connection.class.getClassLoader();// 创建了 Connection的 代理对象 目的是 增强 Connection对象 给他添加了日志功能return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);}/*** return the wrapped connection.** @return the connection*/public Connection getConnection() {return connection;}}

5.4 ConnectionLogger的具体应用

在实际处理的时候,日志模块是如何工作的?

  • 执行sql之前先要获取Statement.
    SimpleExecutor的方法doQuery
/*** 到了 具体的数据库操作的步骤了  JDBC*     Connection*     Statement*     PreparedStatement* @param ms* @param parameter* @param rowBounds* @param resultHandler* @param boundSql* @param <E>* @return* @throws SQLException*/@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 注意,已经来到SQL处理的关键对象 StatementHandler >>  同时会完成  parameterHandler和resultSetHandler的实例化// 默认创建的是 PreparedStatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 获取一个 Statement对象  对占位符处理stmt = prepareStatement(handler, ms.getStatementLog());// 执行查询return handler.query(stmt, resultHandler);} finally {// 用完就关闭closeStatement(stmt);}}
  • 继续看prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 放开了日志 会创建 Connection 的代理对象Connection connection = getConnection(statementLog);// 获取 Statement 对象 ==》 PreparedStatement对象 Statement  如果放开了日志 则会创建 Statement 对应的代理对象stmt = handler.prepare(connection, transaction.getTimeout());// 为 Statement 设置参数  如果是PreparedStatement处理则会对 对应的占位符赋值handler.parameterize(stmt);return stmt;}
  • 继续看getConnection方法,
 protected Connection getConnection(Log statementLog) throws SQLException {// 获取到了真正的 Connection 对象 ? 如果有连接池管理 在此处获取的是PooledConnection 是Connection的代理对象Connection connection = transaction.getConnection();if (statementLog.isDebugEnabled()) {// 创建Connection的日志jdk代理对象return ConnectionLogger.newInstance(connection, statementLog, queryStack);} else {// 返回的是真正的Connection 没有走代理的方式return connection;}}
  • 再看 handler.prepare(connection, transaction.getTimeout()), 进入到instantiateStatement方法
@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();if (keyColumnNames == null) {// connection 是 日志的代理对象return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);} else {// 在执行 prepareStatement 方法的时候会进入进入到ConnectionLogger的invoker方法中return connection.prepareStatement(sql, keyColumnNames);}} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.prepareStatement(sql);} else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}}

在这里插入图片描述

  • 执行查询handler.query(stmt, resultHandler);

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/7182.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA常用高效开发工具—screw一键生成数据库文档(仅需三步)

1.配置 引入screw核心... <!-- screw核心 --> <dependency><groupId>cn.smallbun.screw</groupId><artifactId>screw-core</artifactId><version>1.0.3</version> </dependency><!-- HikariCP --> <dependency…

VuePress在生产环境跳转子页报错 Failed to execute ‘appendChild‘ on ‘Node‘

记录一个使用VuePress时遇到的问题 使用VuePress做了一个文档网页&#xff0c;在开发环境的时候一切正常&#xff0c;但是发布到生产环境后&#xff0c;直接跳转二级页面会报错Failed to execute appendChild on Node 比如主页是http://sun/docs/.vuepress/dist/index.html#/…

【C语言项目】三子棋

文章目录 项目思路一、分文件进行创建二、进入游戏前的目录2.1 目录的功能&#xff1a;2.2 目录界面&#xff1a;2.3 选择进入或退出游戏2.4 多次重玩功能 三、画出棋盘3.1 写出棋子3.2 初始化棋盘3.2 画出棋盘的框架3.3 代码实现 四、玩家落子4.1 落子逻辑4.2具体情况分类讨论…

抖斗音直播间评论引流助手,支持直播间喊话+视频评论区喊话=到指定直播间引流精准粉丝【永久脚本+详细教程】

如果你觉得直播间发言手动太麻烦了&#xff0c;或许这个自动工具能帮到你&#xff01; 1.开始运行前&#xff0c;需要手动去打开打开直播间或者视频评论区&#xff0c;再运行脚本。 2.脚本就是模拟人工操作&#xff0c;在相应的APP里进行评论&#xff0c;无突破APP限制功能。…

【Kubernetes运维篇】ingress-nginx实现业务灰度发布详解

文章目录 一、理论&#xff1a;实现灰度发布的几种场景1、场景一&#xff1a;将新版本灰度给部分用户2、场景二&#xff1a;按照比例流程给新版本3、实现灰度发布字段解释 二、实践&#xff1a;1、实验前提环境2、基于Request Header(请求头)进行流量分割3、基于Cookie进行流量…

93.qt qml-自定义Table优化(新增:水平拖拽/缩放自适应/选择使能/自定义委托)

之前我们更新了90.qt qml-Table表格组件(支持表头表尾固定/自定义颜色/自定义操作按钮/排序)_qml 表格_诺谦的博客-CSDN博客 但是一直没出源码,是因为该demo还存在问题,那就是表头表尾固定下,如果是半透明状态下,会看到表头表尾固定后的内容,所以只能重构代码,不能使用重…

Vue3组合式API+TypeScript写法入门

文章目录 前言1.reactive2.ref3.props4.computed5.emit6.watch总结 前言 参考Vue3官网. 本篇以组合式API为例, 但不包含setup语法糖式写法. 原本打算结合class-component, Vue3不推荐就不用了: OverView|Vue Class Component. 而且是不再推荐基于类的组件写法, 推荐单文件组件…

Android App 持续集成性能测试:启动流量

目录 前言&#xff1a; get app UID 获取流量数据 获得启动流量数据 总结 前言&#xff1a; Jenkins 是一种开源的持续集成工具&#xff0c;可以帮助我们更加方便地进行软件开发和测试工作。通过 API 远程管理 Jenkins 可以帮助我们更加方便地进行 Jenkins 的配置和管理工…

react实现路由跳转动画

下载插件 npm i react-transition-group 配置路由 import { createBrowserRouter as ReactRouter,Navigate } from "react-router-dom";import App from ../App.js import Login from "../view/login.js"; import Home from "../home.js"; co…

了解 3DS MAX 3D摄像机跟踪设置:第 4 部分

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. 项目设置 步骤 1 打开“后效”。 打开后效果 步骤 2 转到合成>新合成以创建新合成。 将“宽度”和“高度”值分别设置为 1280 和 720。将帧速率设置为 25&#xff0c;将持续时间设置为 12 秒。单…

Flask 文件上传,删除上传的文件

目录结构 app.py from flask import Flask, request, render_template, redirect, url_for import osapp Flask(__name__) BASE_DIR os.getcwd() UPLOAD_FOLDER os.path.join(BASE_DIR, testfile)app.route(/) def home():files os.listdir(UPLOAD_FOLDER)return render_t…

欧盟新规,燃油噩梦?2025年起,高速公路每60公里设立一处快充站

根据外媒The Verge报道&#xff0c;欧洲电动汽车用户将获得更多便捷的待遇&#xff0c;同时还能减少有害温室气体排放&#xff0c;这得益于欧盟理事会最新通过的法规。 根据欧盟的法规要求&#xff0c;自2025年起&#xff0c;TEN-T高速公路系统在欧洲将需要每隔60公里设立一座高…

Langchain 和 Chroma 的集成

Langchain 和 Chroma 的集成 1. Chroma2. 基本示例​3. 基本示例(包括保存到磁盘)4. 将 Chroma Client 传递到 Langchain ​5. 基本示例(使用 Docker 容器)6. 更新和删除7. 带分数的相似性搜索​ 1. Chroma Chroma 是一个人工智能原生开源矢量数据库&#xff0c;专注于开发人员…

ES6基础知识六:你是怎么理解ES6中 Promise的?使用场景?

一、介绍 Promise&#xff0c;译为承诺&#xff0c;是异步编程的一种解决方案&#xff0c;比传统的解决方案&#xff08;回调函数&#xff09;更加合理和更加强大 在以往我们如果处理多层异步操作&#xff0c;我们往往会像下面那样编写我们的代码 doSomething(function(resu…

[ 容器 ] Harbor 私有仓库的部署与管理

目录 一、什么是Harbor二、Harbor的特性三、Harbor的构成四、Harbor 部署五、关于 Harbor.cfg 配置文件中有两类参数&#xff1a;所需参数和可选参数六、维护管理Harbor 一、什么是Harbor Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户…

jQuery的DOM操作之笔记总结

jQuery的DOM操作之笔记总结 首先我们来介绍一下什么是DOM 简述&#xff1a; 1.DOM全称Document Object Model&#xff08;文档对象模型&#xff09;。 2.每个文档都是一棵DOM结构的树&#xff0c;文档里的很多元素&#xff0c;就像树上的很多节点&#xff0c;或是分叉的树枝…

知识库数据导出为excel-使用JavaScript实现在浏览器中导出Excel文件

我们智能客服知识库机器人已经开发完成&#xff0c;后端数据库是使用的qdrant向量数据库&#xff0c;但是该数据库并没有导出备份功能&#xff0c;所以我按简单的纯前端实现知识库导出excel数据 使用第三方库(如SheetJS) SheetJS是一个流行的JavaScript库&#xff0c;可帮助处理…

腾讯云 Cloud Studio 实战训练营——快速构建React完成点餐H5页面

目录 ​编辑 一、前言 1、什么是腾讯云 Cloud Studio 2、本文实验介绍 二、前期准备工作 1、注册 Cloud Studio 2、初始化工作空间 三、开发一个简版的点餐系统页面 1、安装依赖 1.1、安装 antd-mobile 1.2、安装 less 和 less-loader 1.3、暴露 webpack 配置文件 …

OpenCV实现照片换底色处理

目录 1.导言 2.引言 3.代码分析 4.优化改进 5.总结 1.导言 在图像处理领域&#xff0c;OpenCV是一款强大而广泛应用的开源库&#xff0c;能够提供丰富的图像处理和计算机视觉功能。本篇博客将介绍如何利用Qt 编辑器调用OpenCV库对照片进行换底色处理&#xff0c;实现更加…

Stable Diffusion生成艺术二维码

Stable Diffusion生成艺术二维码 文章会有浏览问题&#xff0c;点击此处查看原文 首先需要一个Stable Diffusion服务环境&#xff0c;《Stable Diffusion服务环境搭建&#xff08;远程服务版&#xff09;》如果你已经有了那就忽略 一、准备一个比较好的二维码底图 首先解析二…