方法一: 创建一个拦截器即可MybatisSqlLoggerInterceptor,没有其他任何配置
缺依赖使用这个
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.2</version>
</dependency>
package com.rabbiter.sms.config;
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的情况作了处理<br>
*
* @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<ParameterMapping> 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;
}
}
效果如下:
方法二:会用?号替换实际参数,如果使用官方新增的p6spy打印分析sql语句方案则效果和方法类似
参考:mybatis-plus配置控制台打印完整带参数SQL语句
问题背景
Update
2020-Oct : 新增LOG DEBUG LEVEL模式
2020-July : 新增官方p6spy打印分析sql语句方案
解决方案(StdOutImpl)
请注意: 部分朋友反馈不生效,估计跟引入的包有一定关系,druid+mybatis-plus-boot-starter 就亲测有用。请检查是否有log4j相关实现类。
如果是application.yml
#by zhengkai.blog.csdn.net
#mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
如果是application.properties,添加:
#mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种方式:
-
- SLF4J
-
- Apache Commons Logging
-
- Log4j 2
-
- Log4j
-
- JDK logging
-
- no logging
具体选择哪个日志实现由MyBatis的LogFactory内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
不少应用服务器的classpath中已经包含Commons Logging,如Tomcat和WebShpere, 所以MyBatis会把它作为具体的日志实现。
记住这点非常重要。这意味着,在诸如 WebSphere的环境中——WebSphere提供了Commons Logging的私有实现,你的Log4J配置将被忽略。
这种做法不免让人悲摧,MyBatis怎么能忽略你的配置呢?事实上,因Commons Logging已经存 在,按优先级Log4J自然就被忽略了!
控制台输出
--- [ XNIO-1 task-12] c.s.cms.controller.IndexController : username-admin-password-123456-****
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@708e9ffd] was not registered for synchronization because synchronization is not active
--- [ XNIO-1 task-12] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@62b13210] will not be managed by Spring
==> Preparing: select * from user t where t.user_code='admin' and t.password='123456'
==> Parameters:
<== Columns: user_id, user_code, create_date, modify_date, user_name, password, status, role_id, department_id, major_id, classes_id, year
<== Row: 1, admin, 2020-02-15 22:14:32, 2020-02-18 23:38:51, Moshow K ZHENG, 123456, 1, 9, 1, 13, 113, 2020
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@708e9ffd]
解决方案(手写MybatisPlusOutImpl) 配置文件
mybatis-plus:
configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 改为自己写的
log-impl: com.softdev.system.config.MybatisPlusOutImpl
java类 MybatisPlusOutImpl
package com.softdev.system.config;
import org.apache.ibatis.logging.Log;
/**
* @Description MybatisPlusOutImpl,直接使用控制台输出日志
* @Author zhengkai.blog.csdn.net
**/
public class MybatisPlusOutImpl implements Log {
public MybatisPlusOutImpl(String clazz) {
System.out.println("MybatisPlusOutImpl::"+clazz);
}
public boolean isDebugEnabled() {
return true;
}
public boolean isTraceEnabled() {
return true;
}
public void error(String s, Throwable e) {
System.err.println(s);
e.printStackTrace(System.err);
}
public void error(String s) {
System.err.println("MybatisPlusOutImpl::"+s);
}
public void debug(String s) {
System.out.println("MybatisPlusOutImpl::"+s);
}
public void trace(String s) {
System.out.println("MybatisPlusOutImpl::"+s);
}
public void warn(String s) {
System.out.println("MybatisPlusOutImpl::"+s);
}
}
解决方案(LOG-DEBUG模式)
# 在application.yml 中增加配置,指定 mapper 文件所在的包,进入DEBUG模式
logging:
level:
com.baomidou.example.mapper: debug
官方解决方案p6spy(不建议) 查看p6spy最新版本 ,请注意,该方案为侵入式的JDBC级方案。
pom.xml引入
<!-- https://mvnrepository.com/artifact/p6spy/p6spy -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
这是yaml版本,还没试过,待我实验一下.
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:h2:mem:test
...
这是官方提供的properties版本
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,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