spring+mybatis+log4jdbc源码分析!

最近有个需求,记录框架spring+mybatis的项目的慢sql,想到了log4jdbc框架,log4jdbc只是对传统jdbc的一层封装,然后打印出sql执行日志和执行时间:

先简单介绍一下log4jdbc的使用:

第一步:引用log4jdbc的gradle配置,如果是maven的自己转换:

compile 'com.googlecode.log4jdbc:log4jdbc:1.2'

第二步:修改jdbc的driver和url:

mysql:
  datasource:
    driverClassName: net.sf.log4jdbc.DriverSpy
    url: jdbc:log4jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC

就是这么简单,修改了这2个地方,就可以直接使用了,现在只要操作了数据库,日志便会打印出sql和sql的执行时间,以下面的这种格式:


然而具体的执行过程是怎么样的呢,我们有必要先阅读一下log4jdbc的源码,log4jdbc的源码包并不大,只有十几个类:



DriverSpy是对java.sql.Driver接口的实现,ConnectionSpy是对java.sql.Connection接口的实现,先来看看Class.forName(“net.sf.log4jdbc.DriverSpy”)时到底干了什么,先来看看DriverSpy的static静态代码块:

static
{
//log4jdbc初始化开始
 log.debug("... log4jdbc initializing ...");
//从classpath下读取配置文件
  InputStream propStream =
    DriverSpy.class.getResourceAsStream("/log4jdbc.properties");

  Properties props = new Properties(System.getProperties());
  if (propStream != null)
  {
    try
    {
      props.load(propStream);
    }
    catch (IOException e)
    {
      log.debug("ERROR!  io exception loading " +
        "log4jdbc.properties from classpath: " + e.getMessage());
    }
    finally
    {
      try
      {
        propStream.close();
      }
      catch (IOException e)
      {
        log.debug("ERROR!  io exception closing property file stream: " +
          e.getMessage());
      }
    }
    log.debug("  log4jdbc.properties loaded from classpath");
  }
  else
  {
    log.debug("  log4jdbc.properties not found on classpath");
  }

  // look for additional driver specified in properties
//加载dubug模式的前缀
 DebugStackPrefix = getStringOption(props, "log4jdbc.debug.stack.prefix");
  TraceFromApplication = DebugStackPrefix != null;
//当SqlTimingWarnThresholdEnabled 为true时,SqlTimingWarnThresholdMsec 才有效,当sql执行时间大于
//SqlTimingWarnThresholdMsec 时间时,才打印warn日志
  Long thresh = getLongOption(props, "log4jdbc.sqltiming.warn.threshold");
  SqlTimingWarnThresholdEnabled = (thresh != null);
  if (SqlTimingWarnThresholdEnabled)
  {
    SqlTimingWarnThresholdMsec = thresh.longValue();
  }

  thresh = getLongOption(props, "log4jdbc.sqltiming.error.threshold");
  SqlTimingErrorThresholdEnabled = (thresh != null);
  if (SqlTimingErrorThresholdEnabled)
  {
    SqlTimingErrorThresholdMsec = thresh.longValue();
  }

  DumpBooleanAsTrueFalse =
    getBooleanOption(props, "log4jdbc.dump.booleanastruefalse",false);

  DumpSqlMaxLineLength = getLongOption(props,
    "log4jdbc.dump.sql.maxlinelength", 90L).intValue();

  DumpFullDebugStackTrace =
    getBooleanOption(props, "log4jdbc.dump.fulldebugstacktrace",false);

  StatementUsageWarn =
    getBooleanOption(props, "log4jdbc.statement.warn",false);

//默认的打印所有类型的日志,也可以自己在配置文件中配置只打印某一种类型的日志,比如“select”
  DumpSqlSelect = getBooleanOption(props, "log4jdbc.dump.sql.select",true);
  DumpSqlInsert = getBooleanOption(props, "log4jdbc.dump.sql.insert",true);
  DumpSqlUpdate = getBooleanOption(props, "log4jdbc.dump.sql.update",true);
  DumpSqlDelete = getBooleanOption(props, "log4jdbc.dump.sql.delete",true);
  DumpSqlCreate = getBooleanOption(props, "log4jdbc.dump.sql.create",true);

  DumpSqlFilteringOn = !(DumpSqlSelect && DumpSqlInsert && DumpSqlUpdate &&
    DumpSqlDelete && DumpSqlCreate);

  DumpSqlAddSemicolon = getBooleanOption(props,
    "log4jdbc.dump.sql.addsemicolon", false);

//是否加载所有有名气的数据库驱动,默认是加载
  AutoLoadPopularDrivers = getBooleanOption(props,
    "log4jdbc.auto.load.popular.drivers", true);

  TrimSql = getBooleanOption(props, "log4jdbc.trim.sql", true);

  TrimExtraBlankLinesInSql = getBooleanOption(props, "log4jdbc.trim.sql.extrablanklines", true);

  SuppressGetGeneratedKeysException =
    getBooleanOption(props, "log4jdbc.suppress.generated.keys.exception",
    false);

  // The Set of drivers that the log4jdbc driver will preload at instantiation
  // time.  The driver can spy on any driver type, it's just a little bit
  // easier to configure log4jdbc if it's one of these types!

  Set subDrivers = new TreeSet();

 
  if (AutoLoadPopularDrivers)
  {
    subDrivers.add("oracle.jdbc.driver.OracleDriver");
    subDrivers.add("oracle.jdbc.OracleDriver");
    subDrivers.add("com.sybase.jdbc2.jdbc.SybDriver");
    subDrivers.add("net.sourceforge.jtds.jdbc.Driver");

    // MS driver for Sql Server 2000
    subDrivers.add("com.microsoft.jdbc.sqlserver.SQLServerDriver");

    // MS driver for Sql Server 2005
    subDrivers.add("com.microsoft.sqlserver.jdbc.SQLServerDriver");

    subDrivers.add("weblogic.jdbc.sqlserver.SQLServerDriver");
    subDrivers.add("com.informix.jdbc.IfxDriver");
    subDrivers.add("org.apache.derby.jdbc.ClientDriver");
    subDrivers.add("org.apache.derby.jdbc.EmbeddedDriver");
    subDrivers.add("com.mysql.jdbc.Driver");
    subDrivers.add("org.postgresql.Driver");
    subDrivers.add("org.hsqldb.jdbcDriver");
    subDrivers.add("org.h2.Driver");
  }

  // look for additional driver specified in properties
//也可以自己添加不常用的数据库驱动
 String moreDrivers = getStringOption(props, "log4jdbc.drivers");

  if (moreDrivers != null)
  {
    String[] moreDriversArr = moreDrivers.split(",");

    for (int i = 0; i < moreDriversArr.length; i++)
    {
      subDrivers.add(moreDriversArr[i]);
      log.debug ("    will look for specific driver " + moreDriversArr[i]);
    }
  }

  try
  {
   DriverManager.registerDriver(new DriverSpy());
  }
  catch (SQLException s)
  {
    // this exception should never be thrown, JDBC just defines it
    // for completeness
    throw (RuntimeException) new RuntimeException
      ("could not register log4jdbc driver!").initCause(s);
  }

  // instantiate all the supported drivers and remove
  // those not found
  String driverClass;
  for (Iterator i = subDrivers.iterator(); i.hasNext();)
  {
    driverClass = (String) i.next();
    try
    {
 //加载所有驱动,我这里用的mysql,所有只会加载mysql的数据库驱动,所有说DriverSpy只是对传统的
//Driver的封装
     Class.forName(driverClass);
      log.debug("  FOUND DRIVER " + driverClass);
    }
    catch (Throwable c)
    {
      i.remove();
    }
  }

  if (subDrivers.size() == 0)
  {
    log.debug("WARNING!  " +
      "log4jdbc couldn't find any underlying jdbc drivers.");
  }

  SqlServerRdbmsSpecifics sqlServer = new SqlServerRdbmsSpecifics();
  OracleRdbmsSpecifics oracle = new OracleRdbmsSpecifics();
  MySqlRdbmsSpecifics mySql = new MySqlRdbmsSpecifics();

  /** create lookup Map for specific rdbms formatters */
  rdbmsSpecifics = new HashMap();
  rdbmsSpecifics.put("oracle.jdbc.driver.OracleDriver", oracle);
  rdbmsSpecifics.put("oracle.jdbc.OracleDriver", oracle);
  rdbmsSpecifics.put("net.sourceforge.jtds.jdbc.Driver", sqlServer);
  rdbmsSpecifics.put("com.microsoft.jdbc.sqlserver.SQLServerDriver",
    sqlServer);
  rdbmsSpecifics.put("weblogic.jdbc.sqlserver.SQLServerDriver", sqlServer);
  rdbmsSpecifics.put("com.mysql.jdbc.Driver", mySql);

  log.debug("... log4jdbc initialized! ...");
}
注册驱动的初始化基本结束了

接下来我们来看看项目运行的时候是怎么执行打印sql的:

项目使用的是mybatis,所以我们从SimpleExecutor类的doQuery方法作为入口来看程序的执行:

SimpleExecutor:

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
 Configuration configuration = ms.getConfiguration();
//这里是一个RoutingStatementHandler,通过它的构造方法可知delegate=PreparedStatementHandler
 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);    
  stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
我们先进去prepareStatement看看Connection是怎么得到的:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}
BaseExecutor

protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}
SpringManagedTransaction
@Override
public Connection getConnection() throws SQLException {
  if (this.connection == null) {
    openConnection();
  }
  return this.connection;
}
private void openConnection() throws SQLException {
//这里的datasource就是配置文件里面配置的dataSource
 this.connection = DataSourceUtils.getConnection(this.dataSource);
  this.autoCommit = this.connection.getAutoCommit();
  this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

  if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(
        "JDBC Connection ["
            + this.connection
            + "] will"
            + (this.isConnectionTransactional ? " " : " not ")
            + "be managed by Spring");
  }
}
DataSourceUtils
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { try { return doGetConnection(dataSource); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException( "Failed to obtain JDBC Connection", ex); } catch (IllegalStateException ex) { throw new CannotGetJdbcConnectionException( "Failed to obtain JDBC Connection: " + ex.getMessage()); }}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
   Assert.notNull(dataSource, "No DataSource specified");

   ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
   if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
      conHolder.requested();
      if (!conHolder.hasConnection()) {
         logger.debug("Fetching resumed JDBC Connection from DataSource");
         conHolder.setConnection(fetchConnection(dataSource));
      }
      return conHolder.getConnection();
   }
   // Else we either got no holder or an empty thread-bound holder here.

   logger.debug("Fetching JDBC Connection from DataSource");
 //获取Connection的入口
  Connection con = fetchConnection(dataSource);

   if (TransactionSynchronizationManager.isSynchronizationActive()) {
      logger.debug("Registering transaction synchronization for JDBC Connection");
      // Use same Connection for further JDBC actions within the transaction.
      // Thread-bound object will get removed by synchronization at transaction completion.
      ConnectionHolder holderToUse = conHolder;
      if (holderToUse == null) {
         holderToUse = new ConnectionHolder(con);
      }
      else {
         holderToUse.setConnection(con);
      }
      holderToUse.requested();
      TransactionSynchronizationManager.registerSynchronization(
            new ConnectionSynchronization(holderToUse, dataSource));
      holderToUse.setSynchronizedWithTransaction(true);
      if (holderToUse != conHolder) {
         TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
      }
   }

   return con;
}

private static Connection fetchConnection(DataSource dataSource) throws SQLException {
   Connection con = dataSource.getConnection();
   if (con == null) {
      throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
   }
   return con;
}
AbstractDriverBasedDataSource
public Connection getConnection() throws SQLException { return getConnectionFromDriver(getUsername(), getPassword());}
protected Connection getConnectionFromDriver(@Nullable String username, @Nullable String password) throws SQLException {
   Properties mergedProps = new Properties();
   Properties connProps = getConnectionProperties();
   if (connProps != null) {
      mergedProps.putAll(connProps);
   }
   if (username != null) {
      mergedProps.setProperty("user", username);
   }
   if (password != null) {
      mergedProps.setProperty("password", password);
   }
//获取Connection
   Connection con = getConnectionFromDriver(mergedProps);
   if (this.catalog != null) {
      con.setCatalog(this.catalog);
   }
   if (this.schema != null) {
      con.setSchema(this.schema);
   }
   return con;
}
DriverManagerDataSource

@Override protected Connection getConnectionFromDriver(Properties props) throws SQLException { String url = getUrl(); Assert. state(url != null, "'url' not set"); if ( logger.isDebugEnabled()) { logger.debug( "Creating new JDBC DriverManager Connection to [" + url + "]"); } return getConnectionFromDriverManager(url, props);}

DriverManager
@CallerSensitive
public static Connection getConnection(String url,
    java.util.Properties info) throws SQLException {

    return (getConnection(url, info, Reflection.getCallerClass()));
}

private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }

    println("DriverManager.getConnection(\"" + url + "\")");

    // Walk through the loaded registeredDrivers attempting to make a connection.
    // Remember the first exception that gets raised so we can reraise it.
    SQLException reason = null;

//还记得这个参数吗,就是加载数据库驱动的时候初始化的
    for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
          //这才是真正得到Connection的方法,这里调用的就是初始化set进去的DriverSpy的connect方法
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }

        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }

    }

    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }

    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}


DriverSpy

//现在我们知道了最终返回的就是ConnectionSpy对象,但是里面有一个真正的connection
public Connection connect(String url, Properties info) throws SQLException
{
  Driver d = getUnderlyingDriver(url);
  if (d == null)
  {
    return null;
  }

  // get actual URL that the real driver expects
  // (strip off "jdbc:log4" from url)
  url = url.substring(9);

  lastUnderlyingDriverRequested = d;
  Connection c = d.connect(url, info);

  if (c == null)
  {
    throw new SQLException("invalid or unknown driver url: " + url);
  }
  if (log.isJdbcLoggingEnabled())
  {
    ConnectionSpy cspy = new ConnectionSpy(c);
    RdbmsSpecifics r = null;
    String dclass = d.getClass().getName();
    if (dclass != null && dclass.length() > 0)
    {
      r = (RdbmsSpecifics) rdbmsSpecifics.get(dclass);
    }

    if (r == null)
    {
      r = defaultRdbmsSpecifics;
    }
    cspy.setRdbmsSpecifics(r);
    return cspy;
  }
  else
  {
    return c;
  }
}

现在我们再回到得到Connection的入口方法:SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}
现在我们再来看看如何得到PreparedStatementSpy对象的:

BaseStatementHandler

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
  //初始化statement
   statement = instantiateStatement(connection);
    setStatementTimeout(statement, transactionTimeout);
    setFetchSize(statement);
    return statement;
  } catch (SQLException e) {
    closeStatement(statement);
    throw e;
  } catch (Exception e) {
    closeStatement(statement);
    throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
  }
}

PreparedStatementHandler

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  String sql = boundSql.getSql();
  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    String[] keyColumnNames = mappedStatement.getKeyColumns();
    if (keyColumnNames == null) {
      return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    } else {
      return connection.prepareStatement(sql, keyColumnNames);
    }
  } else if (mappedStatement.getResultSetType() != null) {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  } else {
//找到了,就在这里,这里的connection就是前面的ConnectionSpy对象,调用ConnectionSpy对象的 
//PreparedStatementHandler方法:
return connection.prepareStatement(sql); }}
ConnectionSpy
public PreparedStatement prepareStatement(String sql) throws SQLException
{
  String methodCall = "prepareStatement(" + sql + ")";
  try
  {
//这是真是的PreparedStatement 对象
 PreparedStatement statement = realConnection.prepareStatement(sql);
    return (PreparedStatement) reportReturn(methodCall, new PreparedStatementSpy(sql, this, statement));
  }
  catch (SQLException s)
  {
    reportException(methodCall, s, sql);
    throw s;
  }
}
这里返回的是PreparedStatementSpy对象,但是同样的里面有一个真实的PreparedStatement ;

在回到SimpleExecutor的doQuery方法:

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
//prepareStatement真正的执行
   return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}


PreparedStatementHandler

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}

PreparedStatementSpy

public boolean execute() throws SQLException
{
  String methodCall = "execute()";
//拼装sql,就是将?去掉,换上对应的参数
 String dumpedSql = dumpedSql();
//日志打印sql
 reportSql(dumpedSql, methodCall);
  long tstart = System.currentTimeMillis();
  try
  {
  //真实的执行sql
   boolean result = realPreparedStatement.execute();
 //打印sql以及sql执行时间
   reportSqlTiming(System.currentTimeMillis() - tstart, dumpedSql, methodCall);
    return reportReturn(methodCall, result);
  }
  catch (SQLException s)
  {
    reportException(methodCall, s, dumpedSql, System.currentTimeMillis() - tstart);
    throw s;
  }
}

一直跟下去,跟到Slf4jSpyLogDelegator,这个类就是根据配置文件来打印sql以及sql执行时间的日志:

public void sqlTimingOccured(Spy spy, long execTime, String methodCall, String sql)
{
  if (sqlTimingLogger.isErrorEnabled() &&
      (!DriverSpy.DumpSqlFilteringOn || shouldSqlBeLogged(sql)))
  {
    if (DriverSpy.SqlTimingErrorThresholdEnabled &&
        execTime >= DriverSpy.SqlTimingErrorThresholdMsec)
    {
      sqlTimingLogger.error(
        buildSqlTimingDump(spy, execTime, methodCall, sql, sqlTimingLogger.isDebugEnabled()));
    }
    else if (sqlTimingLogger.isWarnEnabled())
    {
      if (DriverSpy.SqlTimingWarnThresholdEnabled &&
        execTime >= DriverSpy.SqlTimingWarnThresholdMsec)
      {
        sqlTimingLogger.warn(
          buildSqlTimingDump(spy, execTime, methodCall, sql, sqlTimingLogger.isDebugEnabled()));
      }
      else if (sqlTimingLogger.isDebugEnabled())
      {
        sqlTimingLogger.debug(
          buildSqlTimingDump(spy, execTime, methodCall, sql, true));
      }
      else if (sqlTimingLogger.isInfoEnabled())
      {
        sqlTimingLogger.info(
          buildSqlTimingDump(spy, execTime, methodCall, sql, false));
      }
    }
  }
}

private String buildSqlTimingDump(Spy spy, long execTime, String methodCall,
                                  String sql, boolean debugInfo)
{
  StringBuffer out = new StringBuffer();

  if (debugInfo)
  {
    out.append(getDebugInfo());
    out.append(nl);
    out.append(spy.getConnectionNumber());
    out.append(". ");
  }

  // NOTE: if both sql dump and sql timing dump are on, the processSql
  // algorithm will run TWICE once at the beginning and once at the end
  // this is not very efficient but usually
  // only one or the other dump should be on and not both.

  sql = processSql(sql);

  out.append(sql);
  out.append(" {executed in ");
  out.append(execTime);
  out.append(" msec}");

  return out.toString();
}
到这里,一切真相大白!

猜你喜欢

转载自blog.csdn.net/chengkui1990/article/details/80446935