最近在做mych(一个MySQL到内存数据库h2的同步工具)时需要在数据操作时打印日志,主要是为了打印sql语句和参数。刚开始我是把日志打印写在各个数据处理工具类中的,主要包括insert、update和delete处理类中。后来觉得这种打印日志的方式对代码的侵入性太高,不仅仅是可读性变差了,更重要的是无法重用这些代码,代码分散在各个数据处理类中,如果新增了数据处理类也需要打印,则需要重新添加代码。




sql serverif else报错 sql if elseif_ide


于是我就想怎么对此做一些优化,突然想到在mybatis中也有类似的需求,何不看看mybatis是如何处理的呢?凭着记忆首先找到了SimpleExecutor类,在org.apache.ibatis.executor包下面,我们看一下doQuery方法,代码如下:

@Override  public  List 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());      return handler.query(stmt, resultHandler);    } finally {      closeStatement(stmt);    }  }

我们可以看到其中调用了prepareStatement方法来创建Statement对象,prepareStatement方法如下:

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;  }

我们可以看到这里调用了getConnection用于创建Connection对象,继续看getConnection方法,这个方法在父类BaseExecutor中,具体代码如下:

protected Connection getConnection(Log statementLog) throws SQLException {    Connection connection = transaction.getConnection();    if (statementLog.isDebugEnabled()) {      return ConnectionLogger.newInstance(connection, statementLog, queryStack);    } else {      return connection;    }  }

这里先从transaction对象获取数据库连接对象,然后判断了日志对象是否开启debug级别,如果开启了则会调用ConnectionLogger.newInstance方法生成一个数据库连接对象,我们看看这个方法是如何实现的:

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);  }

这个方法使用jdk的动态代理功能,生成了Connection类的一个代理对象,根据动态代理规则,最终对该对象的操作都将会调用ConnectionLogger类的invoke方法,动态代理对象可以在此方法中实现我们需要的额外功能,我们看看invoke方法的实现:

@Override  public Object invoke(Object proxy, Method method, Object[] params)      throws Throwable {    try {      if (Object.class.equals(method.getDeclaringClass())) {        return method.invoke(this, params);      }          if ("prepareStatement".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 ("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);    }  }

在这个方法中我们能够清晰的看到,在我们调用prepareStatement方法时,如果日志对象开启了debug级别,则会打印出我们的sql语句,而后生成一个PreparedStatement的代理对象。调用其他方法也是类似的逻辑。除了prepareStatement方法外,调用prepareCall和createStatement方法同样会生成相关的代理类。这里为什么要生成代理类,而不是直接返回被代理对象呢?其实也是为了实现日志打印的相关操作,我们就看一下PreparedStatementLogger的invoke方法具体实现:

@Override  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {    try {      if (Object.class.equals(method.getDeclaringClass())) {        return method.invoke(this, params);      }                if (EXECUTE_METHODS.contains(method.getName())) {        if (isDebugEnabled()) {          debug("Parameters: " + getParameterValueString(), true);        }        clearColumnInfo();        if ("executeQuery".equals(method.getName())) {          ResultSet rs = (ResultSet) method.invoke(statement, params);          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);        } else {          return method.invoke(statement, params);        }      } else if (SET_METHODS.contains(method.getName())) {        if ("setNull".equals(method.getName())) {          setColumn(params[0], null);        } else {          setColumn(params[0], params[1]);        }        return method.invoke(statement, params);      } else if ("getResultSet".equals(method.getName())) {        ResultSet rs = (ResultSet) method.invoke(statement, params);        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);      } else if ("getUpdateCount".equals(method.getName())) {        int updateCount = (Integer) method.invoke(statement, params);        if (updateCount != -1) {          debug("   Updates: " + updateCount, false);        }        return updateCount;      } else {        return method.invoke(statement, params);      }    } catch (Throwable t) {      throw ExceptionUtil.unwrapThrowable(t);    }  }

具体的代码就不一一解释了,大家可以自行去看源码。

通过jdk提供的动态代理功能,我们可以将非业务处理逻辑从当前对象中移除,保证代码的可读、可维护性,同时可以在动态代理类中植入我们想要的其他功能,而这些功能对于被代理对象来说是透明的,尤其适合用来处理框架层面一些业务逻辑。