• myBatis源码解析-日志篇(1)


    上半年在进行知识储备,下半年争取写一点好的博客来记录自己源码之路。在学习源码的路上也掌握了一些设计模式,可所谓一举两得。本次打算写Mybatis的源码解读。

    准备工作

    1. 下载mybatis源码

    下载地址:https://github.com/mybatis/mybatis-3 

    2. 下载mybatis-parent源码

    下载地址:https://github.com/mybatis/parent

    3. 编译

    进入mybatis-paren所在文件夹

    mvn clean install

    进入mybatis所在文件夹

    mvn clean 
    mvn install -Dmaven.test.skip=true

    4. 用IDEA或Eclipse打开mybatis即可

    源码分析-日志模块

    1. 日志基础包

    package org.apache.ibatis.logging;
    
    // mybatis自定义接口,提供四种级别 error->debug->trace->warn
    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);
    }

    如上,mybatis提供了日志的四种级别,error->debug->trace->warn

    2. 从源码可以看到,mybatis提供了如jdk14,log4j,log4j2等日志实现,分析常用的如log4j2源码

    // log4j的适配器
    public class Log4j2Impl implements Log {
    
      // 真正提供日志能力的log4j的日志类
      private Log log;
      // 构造方法,导入真正的实现类
      public Log4j2Impl(String clazz) {
        Logger logger = LogManager.getLogger(clazz);
    
        if (logger instanceof AbstractLogger) {
          log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
        } else {
          log = new Log4j2LoggerImpl(logger);
        }
      }
      
      public boolean isDebugEnabled() {
        return log.isDebugEnabled();
      }
    
      public boolean isTraceEnabled() {
        return log.isTraceEnabled();
      }
      // 调用真正的实现方法
      public void error(String s, Throwable e) {
        log.error(s, e);
      }
     // 调用真正的实现方法
      public void error(String s) {
        log.error(s);
      }
    // 调用真正的实现方法
      public void debug(String s) {
        log.debug(s);
      }
    // 调用真正的实现方法
      public void trace(String s) {
        log.trace(s);
      }
    // 调用真正的实现方法
      public void warn(String s) {
        log.warn(s);
      }
    
    }

    综上,可以看到,mybatis并没有提供真正的日志实现接口,只是定义了一套自己的日志接口,其实现交给真正的具体日志类(如log4j,log4j2)。此处用到了适配器模式。比如log4j2,mybatis提供自定义接口,提供log4j2适配器继承自定义接口,在log4j2适配器里调用log4j2的真实方法来实现自己的接口。

    3.各日志默认调用流程

    看源码包中有很多日志实现,那具体的默认调用流程是怎样。查看org.apache.ibatis.logging包中的LogFactory类。看名字就知道是用到了工厂模式。

    
    

    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;

    
    

    // 自动扫描日志实现,并且第三方日志插件加载优先级如下
    // 类加载时会默认实现静态方法,实现顺序为slf4j->commonsLoging->log4j2->log4j->jdklog
    static {
    // 调用tryImplementation方法
    tryImplementation(new Runnable() {
    public void run() {
    useSlf4jLogging();
    }
    });
    tryImplementation(new Runnable() {
    public void run() {
    useCommonsLogging();
    }
    });
    tryImplementation(new Runnable() {
    public void run() {
    useLog4J2Logging();
    }
    });
    tryImplementation(new Runnable() {
    public void run() {
    useLog4JLogging();
    }
    });
    tryImplementation(new Runnable() {
    public void run() {
    useJdkLogging();
    }
    });
    tryImplementation(new Runnable() {
    public void run() {
    useNoLogging();
    }
    });
    }

    ......

    LogFactory有默认的日志加载顺序,写在静态代码块中。默认实现顺序为slf4j->commonsLoging->log4j2->log4j->jdklog。接着分析tryImplementation方法

    // 此方法调用的是线程的run方法,仔细看,是直接run,不是start,所以不是多线程
      private static void tryImplementation(Runnable runnable) {
        // 判断全局的日志构造方法是否为空,若为空,则调用线程的run方法
        if (logConstructor == null) {
          try {
            runnable.run();
          } catch (Throwable t) {
             // 对没有具体实现的日志类,直接忽略,不抛异常,
            // ignore
          }
        }
      }

    查看具体的run方法,此处继续分析log4j2的run方法。

     public static synchronized void useLog4J2Logging() {
        setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
      }
    
    
    
     // 使用到了java的反射机制
      private static void setImplementation(Class<? extends Log> implClass) {
        try {
          // 利用反射获取log4j2的真实构造方法
          Constructor<? extends Log> candidate = implClass.getConstructor(new Class[] { String.class });
          // 获取log4j2的实例
          Log log = candidate.newInstance(new Object[] { LogFactory.class.getName() });
          log.debug("Logging initialized using '" + implClass + "' adapter.");
          // 赋值给全局变量logConstructor
          logConstructor = candidate;
        } catch (Throwable t) {
          throw new LogException("Error setting Log implementation.  Cause: " + t, t);
        }
      }

    以log4j2为例,分析了具体的日志实现。但我们使用mybatis时通常会在执行sql的时候,对sql进行打印输出。所以接下来查看mybatis如何使用这些日志接口。

    4. 分析org.apache.ibatis.logging.jdbc下的类

    在源码分析前,回顾下jdbc连接数据库方式。

             ........
             Class.forName("com.mysql.jdbc.Driver");
             //2.获得数据库链接
             Connection conn=DriverManager.getConnection(URL, USER, PASSWORD);
             //3.通过数据库的连接操作数据库,实现增删改查(使用Statement类)
             Statement st=conn.createStatement();
             ResultSet rs=st.executeQuery("select * from user");
             .........

    加载数据库忽略,此处有Connection,Statement,ResultSet等参数,查看org.apache.ibatis.logging.jdbc下的类,都有对应的log类,先分析BaseJdbcLogger类。

    public abstract class BaseJdbcLogger {
      // PrepareedStatement下的所有set方法
      protected static final Set<String> SET_METHODS = new HashSet<String>();
      protected static final Set<String> EXECUTE_METHODS = new HashSet<String>();
      // PrepareecdStatement 设置的key value pair
      private Map<Object, Object> columnMap = new HashMap<Object, Object>();
      // PreparedStatement 设置的column
      private List<Object> columnNames = new ArrayList<Object>();
      // PrepareedStatement 设置的value
      private List<Object> columnValues = new ArrayList<Object>();
    
      protected Log statementLog;
      protected int queryStack;
    
      /*
       * Default constructor
       */
      public BaseJdbcLogger(Log log, int queryStack) {
        this.statementLog = log;
        if (queryStack == 0) queryStack = 1;
        this.queryStack = queryStack;
      }
      // 初始化默认的一些set方法
      static {
        SET_METHODS.add("setString");
        SET_METHODS.add("setInt");
        SET_METHODS.add("setByte");
        SET_METHODS.add("setShort");
        SET_METHODS.add("setLong");
        SET_METHODS.add("setDouble");
        SET_METHODS.add("setFloat");
        SET_METHODS.add("setTimestamp");
        SET_METHODS.add("setDate");
        SET_METHODS.add("setTime");
        SET_METHODS.add("setArray");
        SET_METHODS.add("setBigDecimal");
        SET_METHODS.add("setAsciiStream");
        SET_METHODS.add("setBinaryStream");
        SET_METHODS.add("setBlob");
        SET_METHODS.add("setBoolean");
        SET_METHODS.add("setBytes");
        SET_METHODS.add("setCharacterStream");
        SET_METHODS.add("setClob");
        SET_METHODS.add("setObject");
        SET_METHODS.add("setNull");
    
        EXECUTE_METHODS.add("execute");
        EXECUTE_METHODS.add("executeUpdate");
        EXECUTE_METHODS.add("executeQuery");
        EXECUTE_METHODS.add("addBatch");
      }
      // setColumn方会记录设置的column和对应的value
      protected void setColumn(Object key, Object value) {
        columnMap.put(key, value);
        columnNames.add(key);
        columnValues.add(value);
      }

    再来分析ConnectionLogger类

    // 实现InvocationHandler就知道是个代理类
    public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
      // 真正的connection
      private Connection connection;
    
      private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.connection = conn;
      }
      // 增强
      public Object invoke(Object proxy, Method method, Object[] params)
          throws Throwable {
        try {
          // 若是从Object继承的方法直接忽略
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, params);
          }
          // 若是prepareStatement方法
          if ("prepareStatement".equals(method.getName())) {
            // 打印sql参数
            if (isDebugEnabled()) {
              debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
            }
            // 调用connection真实方法
            PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
            // 将生成的PreparedStatement也构建成代理对象
            stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
            return stmt;
          } else if ("prepareCall".equals(method.getName())) { // 若是prepareCall方法
            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())) { // 若是createStatement方法
            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();
        return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
      }
    
      /*
       * return the wrapped connection
       *
       * @return the connection
       */
      public Connection getConnection() {
        return connection;
      }
    
    }

    后续如PreparedStatementLogger,ResultSetLogger等也是一样,都是封装了PreparedStatement或ResultSet,在执行真实语句前后进行日志打印,打印执行的Sql语句,此处用到了代理模式。熟悉AOP的可能对此有了解。

  • 相关阅读:
    MySQL慢查询日志总结
    SQL Server 关于列的权限控制
    Oracle global database name与db link的纠缠关系
    TCP Provider The semaphore timeout period has expired
    SQL SERVER 中如何用脚本管理作业
    Unable to determine if the owner (DomainUserName) of job JOB_NAME has server access
    TNS-12535: TNS:operation timed out案例解析
    ORA-12154 & TNS-03505 案例分享
    MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间
    查看数据库表的数据量和SIZE大小的脚本修正
  • 原文地址:https://www.cnblogs.com/xiaobingblog/p/13377045.html
Copyright © 2020-2023  润新知