温故而知新-MyBatis打印完整sql-新方式

本文介绍了如何通过MyBatis的ConnectionLogger和JavassistUtil实现动态代理,以打印出完整的SQL语句,包括参数。通过在ClientPreparedStatement的execute方法中插入自定义逻辑,利用Java动态代理和Javassist库,能够在执行SQL前获取并打印出填充好参数的SQL,方便调试和问题排查。
摘要由CSDN通过智能技术生成

寻找突破点


	打印完整sql的好处不用多说,参数多的sql福音
	找准切入点很关键
	ClientPreparedStatement 是 Statement 的标准实现
	所有 mysql 的连接都会调用它执行查询语句
	里面的 toString() 方法可是福音
	它会完整打印 sql (填充好的查询参数)
	试想只要我们在它执行 execute() 方法时织入我们 toString()方法那就大功告成了
	

织入打印sql逻辑


	想要织入自己的逻辑首先想到的是采用动态代理
	翻阅 MyBatis 源码发现 ConnectionLogger 这个类已经实现了代理 PreparedStatement 逻辑
	我们只要在它上面增砖添瓦那不就很 OK 吗
	要修改这个类 最简单直接的方法 
	在自己项目下新建包 org.apache.ibatis.logging.jdbc
	并新建 ConnectionLogger 直接复写里面的逻辑
	这样 MyBatis 加载 ConnectionLogger 就会加载自己写的

ConnectionLogger 代理逻辑


/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.logging.jdbc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.reflection.ExceptionUtil;

import cn.com.yunbaoxiao.config.JavassistUtil;

/**
 * Connection proxy to add logging.
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 *
 */
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
	
	private final Connection connection;
	
	private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
		super(statementLog, queryStack);
		this.connection = (Connection) conn;
		// this.connection = (Connection) proxyConnect(conn);
	}
	
	/**
	 * 代理PreparedStatement打印sql
	 * 
	 * @param conn
	 * @return PreparedStatement
	 */
	@SuppressWarnings("unused")
	private static Connection proxyConnect(Connection conn) {
		Connection newProxyInstance = (Connection) Proxy.newProxyInstance(conn.getClass()
				.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						// 拦截prepareStatement方法代理ClientPreparedStatement
						if (JavassistUtil.PREPARE_STATEMENT.equals(method.getName())) {
							PreparedStatement stmt = (PreparedStatement) method.invoke(conn, args);
							return JavassistUtil.proxyStmt(stmt);
						}
						return method.invoke(conn, args);
					}
				});
		return newProxyInstance;
		
	}
	
	@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 (JavassistUtil.PREPARE_STATEMENT.equals(method.getName())
					|| "prepareCall".equals(method.getName())) {
				if (isDebugEnabled()) {
					debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
				}
				PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
				// 这里代理HikariProxyPreparedStatement
				stmt = PreparedStatementLogger
						.newInstance(JavassistUtil.proxyStmt(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
	 * @param statementLog the statement log
	 * @param queryStack   the query stack
	 * @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;
	}
}


JavassistUtil


package cn.com.xxx.xxx;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.List;
/**
 * sql打印
 * 
 * @author aming
 * @since 2022-03-02
 */
public class JavassistUtil {
	
	/** 拦截prepareStatement方法代理ClientPreparedStatement */
	public static final String PREPARE_STATEMENT = "prepareStatement";
	
	/** ClientPreparedStatement.toString() */
	public static final String TO_STRING = "toString";
	
	/** ClientPreparedStatement package name */
	public static final String STATEMEN_NAME = "com.mysql.cj.jdbc.ClientPreparedStatement: ";
	
	/** 代理sql执行方法 */
	public static final List<String> AGENTME_THODS = Arrays
			.asList("execute", "executeUpdate", "executeQuery");
	
	/**
	 * 代理PreparedStatement打印sql
	 * 
	 * @param stmt
	 * @return PreparedStatement
	 */
	public static final PreparedStatement proxyStmt(PreparedStatement stmt) {
		return (PreparedStatement) Proxy.newProxyInstance(stmt.getClass().getClassLoader(),
				stmt.getClass().getInterfaces(),
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						// 重写executeQuery方法
						if (AGENTME_THODS.contains(method.getName())) {
							String sql = stmt.toString();
							if (sql.contains(STATEMEN_NAME)) {
								sql = sql.substring(
										sql.indexOf(STATEMEN_NAME) + STATEMEN_NAME.length());
							}
							printSQL(sql);
						}
						return method.invoke(stmt, args);
					}
				});
	}
	
	/**
	 * printSQL
	 * 
	 * @param sqlString void
	 */
	public static final void printSQL(String sqlString) {
		System.out.println("\033[32;4m"
				+ "\r\n;;-- ------------------------------------------------------------------------------------------------------------------------------\r\n"
					+ sqlString
					+ ";"
					+ "\r\n;;-- ------------------------------------------------------------------------------------------------------------------------------\r\n"
					+ "\033[0m");
	}
}

	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值