通常我们开发的时候,需要联合控制台和Navicat/PLSQL等工具进行语句的拼接检查,如果只是输出了一堆???,那么将极大降低我们的效率。因此我们需要输出完整的SQL语句以便调试。
import java.sql.Connection;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.regex.Matcher;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.stereotype.Component;
/**
* 拦截StatementHandler类中参数类型为Statement的 prepare 方法
* 即拦截 Statement prepare(Connection var1, Integer var2) 方法
*/
@Component
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisSqlLoggerInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
//优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
MetaObject metaObject = MetaObject
.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
//先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
//id为执行的mapper方法的全路径名,如com.uv.dao.UserMapper.insertUser
String id = mappedStatement.getId();
log.info("id ==> " + id);
//sql语句类型 select、delete、insert、update
String sqlCommandType = mappedStatement.getSqlCommandType().toString();
log.info("类型 ==> " + sqlCommandType);
BoundSql boundSql = statementHandler.getBoundSql();
// 获取节点的配置
Configuration configuration = mappedStatement.getConfiguration();
// 获取到最终的sql语句
String newsql = getSql(configuration, boundSql, id);
log.info("拦截的sql ==>: " + newsql);
long start = System.currentTimeMillis();
Object returnValue = invocation.proceed();
long end = System.currentTimeMillis();
long time = (end - start);
log.info("sql耗时 ==>: " + time);
return returnValue;
//return null;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 封装了一下sql语句,
* 使得结果返回完整xml路径下的sql语句节点id + sql语句
*
* @param configuration
* @param boundSql
* @param sqlId
* @return
*/
private String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {
String sql = showSql(configuration, boundSql);
StringBuilder str = new StringBuilder(100);
str.append(sqlId);
str.append(":");
str.append(sql);
return str.toString();
}
/**
* 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号;
* 对参数是null和不是null的情况作了处理
*
* @param obj
* @return
*/
private String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
/**
* 进行?的替换
* @param configuration
* @param boundSql
* @return
*/
public String showSql(Configuration configuration, BoundSql boundSql) {
// 获取参数
Object parameterObject = boundSql.getParameterObject();
List parameterMappings = boundSql
.getParameterMappings();
// sql语句中多个空格都用一个空格代替
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) {
// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
// 如果根据parameterObject.getClass()可以找到对应的类型,则替换
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
//MetaObject主要是封装了originalObject对象,
// 提供了get和set的方法用于获取和设置originalObject的属性值,
// 主要支持对JavaBean、Collection、Map三种类型对象的操作
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
// 该分支是动态sql
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else {
//打印出缺失,提醒该参数缺失并防止错位
sql = sql.replaceFirst("\\?", "缺失");
}
}
}
}
return sql;
}
}
c.p.p.a.i.MybatisSqlLoggerInterceptor : 拦截的sql ==>: com.liu.test.admin.mapper.SysDictMapper.selectById:SELECT id,type,description,create_time,update_time,`system`,remarks,del_flag FROM sys_dict WHERE id=3 AND del_flag='0'
2021-07-06 16:00:46.463 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : sql耗时 ==>: 103
2021-07-06 16:00:46.788 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : id ==> com.liu.test.admin.mapper.SysDictMapper.updateById
2021-07-06 16:00:46.790 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : 类型 ==> UPDATE
2021-07-06 16:00:46.793 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : 拦截的sql ==>: com.liu.test.admin.mapper.SysDictMapper.updateById:UPDATE sys_dict SET type='22', description='ssfd55622', create_time=2021-07-06T13:52:55, update_time=2021-07-06T13:52:55, `system`='0', remarks='dsdfd5562247788' WHERE id=3 AND del_flag='0'
2021-07-06 16:00:46.795 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : sql耗时 ==>: 0
2021-07-06 16:00:47.830 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : id ==> com.liu.test.admin.mapper.SysLogMapper.insert
2021-07-06 16:00:47.836 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : 类型 ==> INSERT
2021-07-06 16:00:47.842 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : 拦截的sql ==>: com.liu.test.admin.mapper.SysLogMapper.insert:INSERT INTO sys_log ( type, title, create_by, remote_addr, user_agent, request_uri, method, params, time, service_id ) VALUES ( '0', '修改字典', 'admin', '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', '/dict', 'PUT', '', 1754, 'pig' )
2021-07-06 16:00:47.846 INFO 27608 --- [ XNIO-1 task-1] c.p.p.a.i.MybatisSqlLoggerInterceptor : sql耗时 ==>: 2
p6spy
p6spy
3.9.0
# 数据源
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
username: root
password: root
url: jdbc:p6spy:mysql://pig-mysql:3307/pig?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
# SQl语句分析插件配置
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
Consume Time:76 ms 2021-07-06 16:50:29
Execute SQL:INSERT INTO sys_dict ( type, description, `system`, remarks ) VALUES ( 'tt', 'tt', '0', 'ttt' )
Consume Time:46 ms 2021-07-06 16:50:30
Execute SQL:INSERT INTO sys_log ( type, title, create_by, remote_addr, user_agent, request_uri, method, params, time, service_id ) VALUES ( '0', '添加字典', 'admin', '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36', '/dict', 'POST', '', 3023, 'pig' )
Consume Time:8 ms 2021-07-06 16:50:30
Execute SQL:SELECT COUNT(*) FROM sys_dict WHERE del_flag = '0'
Consume Time:6 ms 2021-07-06 16:50:30
Execute SQL:SELECT id,type,description,create_time,update_time,`system`,remarks,del_flag FROM sys_dict WHERE del_flag='0' LIMIT 20
#mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
源码如下
package com.baomidou.mybatisplus.core;
/*
* Copyright (c) 2011-2021, baomidou ([email protected]).
*
* 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.
*/
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
/**
* 自定义 ParameterHandler 重装构造函数,填充插入方法主键 ID
*
* @author nieqiuqiu 2020/6/5
* @since 3.4.0
*/
public class MybatisParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
private final SqlCommandType sqlCommandType;
public MybatisParameterHandler(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.mappedStatement = mappedStatement;
this.boundSql = boundSql;
this.configuration = mappedStatement.getConfiguration();
this.sqlCommandType = mappedStatement.getSqlCommandType();
this.parameterObject = processParameter(parameter);
}
public Object processParameter(Object parameter) {
/* 只处理插入或更新操作 */
if (parameter != null
&& (SqlCommandType.INSERT == this.sqlCommandType || SqlCommandType.UPDATE == this.sqlCommandType)) {
//检查 parameterObject
if (ReflectionKit.isPrimitiveOrWrapper(parameter.getClass())
|| parameter.getClass() == String.class) {
return parameter;
}
Collection
输出完整的sql-------------------->>>INSERT INTO sys_dict ( type,
description,
system
,
remarks ) VALUES ( ‘rr’,
‘ggg’,
‘0’,
‘rrr’ )