从零开始SpringCloud Alibaba实战(44)——mybatis 打印日志

前言

在程序开发过程中,为了调试方便、了解程序的运行过程,进行必要的日志输出总是免不了的。对于使用Mybatis而言,我们常见的需求是希望可以在日志中打印出Mybatis执行过程中进行数据库操作的SQL语句及其传递的参数。Mybatis的日志输出是统一管理的,它有自己的日志接口,然后在需要进行日志输出的时候使用统一的API进行日志输出。这个统一的接口是org.apache.ibatis.logging.Log。Mybatis分别基于常用的日志输出工具给出了对应的实现,比如LOG4J、SLF4J等。默认情况下Mybatis的org.apache.ibatis.logging.LogFactory会按照以下顺序依次判断当前程序下可以使用哪种日志实现,直到找到为止,如果一个实现都没有那就是最后的noLogging了,将采用NoLoggingImpl实现。

上文我们介绍了项目中如何打印日志
mybatis 拦截器打印完整sql实现

日志配置入口

 static {

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useSlf4jLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useCommonsLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useLog4J2Logging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useLog4JLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useJdkLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useNoLogging();

      }

    });

  }

上述的默认机制有的时候可能不能满足你的需求,比如有的时候可能你是想使用Commons-Logging,但是因为类路径中包含SLF4J的包,结果Mybatis就自动使用了SLF4J了,所以如果有自己明确想使用的日志实现时,我们应该自己来指定日志实现,这可以通过在Mybatis的全局配置文件中通过如下进行配置,这里的value是一个日志实现的别名,可选值有SLF4J、COMMONS_LOGGING、LOG4J、LOG4J2、JDK_LOGGING、STDOUT_LOGGING、NO_LOGGING,如果希望使用的日志输出实现是Mybatis自身没有实现的,我们也可以自己来实现,只需要自己定义一个类实现Mybatis的org.apache.ibatis.logging.Log接口,然后在下面的配置中把value属性改为自己的实现类的全路径名称即可。

mybatis怎么打印出sql

我们把java.sql.*,com.ibatis等的日志级别都配置成debug的,把sql打印出来了。但打印sql与这些java.sql,com.ibatis包半毛钱关系都没有。至打印日志的对象是org.apache.ibatis.logging.slf4j.Slf4jImpl(statementLog),而statementLog又是在系统启动时,扫描的logId(如com.**.Mapper.query)作为其构造参数的。是否打印日志就是这个参数是否与log4j.xml,与log4j.properties里的log4j.logger匹配来决定的。下面通过源码来分析整个过程:

SQL及执行结果日志输出

概述

每个MappedStatement创建一个日志对象,可以控制每个运行时SQL语句的打印以及执行结果的打印。
日志Debug级别打印SQL语句:PreparedStatement(prepareStatement、prepareCall)创建时打印SQL语句,执行时打印参数值。Statement执行时打印SQL语句。
日志Trace级别打印执行结果。

一、MappedStatement

1.1 构建MappedStatement时指定日志
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
mappedStatement.configuration = configuration;
// 命名空间.statementId即Mapper接口完全限定名.方法名
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.parameterMap = new ParameterMap.Builder(configuration, “defaultParameterMap”, null, new ArrayList()).build();
mappedStatement.resultMaps = new ArrayList();
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
String logId = id;
// 配置日志前缀
if (configuration.getLogPrefix() != null) {
** logId = configuration.getLogPrefix() + id;**
}
// 每个MappedStatement(每个SQL语句)创建一个日志对象
** mappedStatement.statementLog = LogFactory.getLog(logId);**
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}

二、BaseExecutor

2.1 创建Connection
// SimpleExecutor、BatchExecutor、ReuseExecutor调用该方法获取Connection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
// MappedStatement开启Debug级别日志
if (statementLog.isDebugEnabled()) {
// 具有日志打印功能的Connection
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}

三、ConnectionLogger

3.1 创建Connection代理实例
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);
}

3.2 创建日志打印功能的Statement
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级别打印SQL语句
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级别打印SQL语句
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);
}
}

四、StatementLogger

4.1 创建Statement代理实例
public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) {
InvocationHandler handler = new StatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = Statement.class.getClassLoader();
return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler);
}

4.2 创建日志打印功能的ResultSet
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}

// execute、executeUpdate、executeQuery、addBatch
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
// 日志Debug级别打印SQL语句
** debug(" Executing: " + removeBreakingWhitespace((String) params[0]), true);**
}
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 (“getResultSet”.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);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}

五、PreparedStatementLogger

5.1 创建PreparedStatement代理实例
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}

5.2 创建日志打印功能的ResultSet
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}

// execute、executeUpdate、executeQuery、addBatch
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
// 日志Debug级别打印参数值
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);
}
}

六、ResultSetLogger 日志TRACE级别打印执行结果

6.1 创建ResultSet代理实例
public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) {
InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack);
ClassLoader cl = ResultSet.class.getClassLoader();
return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler);
}

6.2 打印执行结果
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
Object o = method.invoke(rs, params);
if (“next”.equals(method.getName())) {
if (((Boolean) o)) {
rows++;
if (isTraceEnabled()) {
ResultSetMetaData rsmd = rs.getMetaData();
final int columnCount = rsmd.getColumnCount();
if (first) {
first = false;
printColumnHeaders(rsmd, columnCount);
}
printColumnValues(columnCount);
}
} else {
debug(" Total: " + rows, false);
}
}
clearColumnInfo();
return o;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}

6.3 打印列头
private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException {
StringBuilder row = new StringBuilder();
row.append(" Columns: “);
for (int i = 1; i <= columnCount; i++) {
if (BLOB_TYPES.contains(rsmd.getColumnType(i))) {
blobColumns.add(i);
}
String colname = rsmd.getColumnLabel(i);
row.append(colname);
if (i != columnCount) {
row.append(”, ");
}
}
trace(row.toString(), false);
}

6.4 打印列值
private void printColumnValues(int columnCount) {
StringBuilder row = new StringBuilder();
row.append(" Row: “);
for (int i = 1; i <= columnCount; i++) {
String colname;
try {
if (blobColumns.contains(i)) {
colname = “<>”;
} else {
colname = rs.getString(i);
}
} catch (SQLException e) {
// generally can’t call getString() on a BLOB column
colname = “<>”;
}
row.append(colname);
if (i != columnCount) {
row.append(”, ");
}
}
trace(row.toString(), false);
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Spring Cloud 中使用 MyBatis 需要进行如下配置: 1. 引入 MyBatis 依赖 在 `pom.xml` 中添加 MyBatisMyBatis-Spring 的依赖: ```xml <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis-spring.version}</version> </dependency> ``` 其中,`${mybatis.version}` 和 `${mybatis-spring.version}` 分别为 MyBatisMyBatis-Spring 的版本号。 2. 配置数据源 在 `application.yml` 中配置数据源信息,例如: ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/mydb username: root password: root driver-class-name: com.mysql.jdbc.Driver ``` 其中,`url`、`username`、`password` 和 `driver-class-name` 分别为数据库连接 URL、用户名、密码和 JDBC 驱动程序的类名。 3. 创建 MyBatis Mapper 创建 MyBatis Mapper,用于定义 SQL 语句和映射关系。例如: ```java // UserMapper.java public interface UserMapper { User selectUserById(Integer id); } ``` 4. 配置 MyBatis 在 Spring Boot 应用程序的配置类中添加 MyBatis 的配置: ```java @Configuration @MapperScan("com.example.mapper") public class MyBatisConfig { @Autowired private DataSource dataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); return sessionFactory.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } } ``` 其中,`@MapperScan` 注解用于指定 MyBatis Mapper 的包路径,`SqlSessionFactory` 和 `SqlSessionTemplate` 分别用于创建和管理 MyBatis 的会话对象。 5. 使用 MyBatis 在代码中使用 MyBatis,例如: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User getUserById(Integer id) { return userMapper.selectUserById(id); } } ``` 在以上代码中,通过 `@Autowired` 注解注入 `UserMapper`,然后调用 `selectUserById` 方法执行 SQL 查询。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值