在项目开发阶段,执行sql语句有利于我们直观排查问题以方便解决问题。但是mybatis默认的方式都是?参数,不方便我们,这个就是默认的方式,需要开启配置

logging:
  level:
    com.baomidou.mybatisplus: debug # MP核心包日志
    com.xxx.scct.dao: debug # 你的Mapper包路径
    org.apache.ibatis: trace # MyBatis原生日志

默认打印的sql,效果如下 

那有没有让我们可以直接看到完整拼接好的sql语句输出打印呢?

第一种方式:p6spy

pom文件添加依赖

  <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.9.1</version>
        </dependency>

resource目录添加spy.properties

module.log=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,batch,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 真实JDBC driver , 多个以 逗号 分割 默认为空
#driverlist=org.h2.Driver
driverlist=com.mysql.cj.jdbc.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

nacos或项目配置文件配置

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
spring.datasource.url=jdbc:p6spy:mysql://192.168.6.167:3306/scct?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8

logback-spring.xml配置添加

    <logger name="com.p6spy" level="debug" additivity="false">
        <appender-ref ref="console"/>    <!-- 这里根据自己文件定义控制台的appender名称替换 -->
        <appender-ref ref="flatfile"/>  <!-- 这里根据自己文件定义日志文件的appender名称替换 -->
    </logger>

    <!-- 防止太多日志 -->
    <logger name="com.p6spy.engine.logging" level="INFO"/>
    <logger name="com.p6spy.engine.spy" level="INFO"/>

执行业务代码,控制台打印效果如下 

第二种方式:log4jdbc

pom文件添加依赖

   <dependency>
            <groupId>com.googlecode.log4jdbc</groupId>
            <artifactId>log4jdbc4</artifactId>
        </dependency>

nacos或项目配置文件配置

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=net.sf.log4jdbc.DriverSpy
spring.datasource.url=jdbc:log4jdbc:mysql://192.168.6.167:3306/scct?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8

resource目录添加log4jdbc.properties

# 基本配置
log4jdbc.auto.load.popular.drivers=true
log4jdbc.drivers=com.mysql.cj.jdbc.Driver

# 慢查询相关配置
log4jdbc.sqltiming.enable=true
# 在日志中添加执行时间
log4jdbc.sqltiming.addTime=true
# 警告阈值 (毫秒)
log4jdbc.sqltiming.warn.threshold=300
# 错误阈值 (毫秒)
log4jdbc.sqltiming.error.threshold=2000

# SQL 执行时间显示格式 # "executed in XX ms"
log4jdbc.sqltiming.timeFormat=enabled
# 包括参数值
log4jdbc.sqltiming.logParameter=true

# 结果集限制(防止大结果集占用过多内存)
log4jdbc.dump.sql.datalimit=1000

logback-spring.xml配置添加

  <logger name="jdbc.resultset" additivity="false">
        <level value="error"/>
        <appender-ref ref="console"/>
        <appender-ref ref="flatfile"/>
    </logger>

    <logger name="jdbc.connection" additivity="false">
        <level value="error"/>
        <appender-ref ref="console"/>
        <appender-ref ref="flatfile"/>
    </logger>

    <logger name="jdbc.audit" additivity="false">
        <level value="error"/>
        <appender-ref ref="console"/>
        <appender-ref ref="flatfile"/>
    </logger>

    <!-- log SQL with timing information, post execution -->
    <logger name="jdbc.sqlonly" additivity="false">
        <level value="off"/> <!--这里设置off来禁用sqlonly打印通过,不然就会导致sql重复打印-->
        <appender-ref ref="console"/>
        <appender-ref ref="flatfile"/>
    </logger>

   <logger name="jdbc.sqltiming" additivity="false">
        <level value="info"/> <!--生产环境一般设置warn,info这里为了调试-->
        <appender-ref ref="console"/>
        <appender-ref ref="flatfile"/>
    </logger>

查看执行效果

第三种方式:代码编写sql拦截器

相关拦截器代码

@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlPrinterInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger("SQL_LOGGER");
    private static final Pattern REPLACE_PATTERN = Pattern.compile("\\?");

    // 配置开关(可通过配置中心动态更新)
    private volatile boolean enabled = true;
    
    // SQL模板缓存(提升性能)
    private final Map<String, String> sqlCache = new ConcurrentHashMap<>(256);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (!enabled || !logger.isDebugEnabled()) {
            return invocation.proceed(); // 修正方法名:proceed()
        }
        
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        MappedStatement mappedStatement = getMappedStatement(handler);
        
        try {
            // 获取原始SQL模板
            String originalSql = boundSql.getSql();
            String cacheKey = mappedStatement.getId() + "@" + originalSql.hashCode();
            String formattedSql = sqlCache.computeIfAbsent(cacheKey, k -> 
                originalSql.replaceAll("[\\s\n]+", " ")
            );
            
            // 获取实际参数值
            Object paramObj = boundSql.getParameterObject();
            
            // 处理批量操作场景
            if (isBatchOperation(boundSql)) {
                handleBatchOperation(invocation, handler, boundSql, formattedSql);
                return invocation.proceed(); // 确保调用继续
            }
            
            // 生成完整SQL
            String completeSql = formatSql(formattedSql, boundSql, getConfiguration(handler));
            logger.debug("执行SQL: {}", completeSql);
        } catch (Exception e) {
            logger.warn("SQL打印失败,不中断操作", e);
        }
        return invocation.proceed();
    }

    private String formatSql(String sql, BoundSql boundSql, Configuration configuration) {
        if (boundSql.getParameterObject() == null) {
            return sql;
        }
        
        List<ParameterMapping> mappings = boundSql.getParameterMappings();
        if (mappings == null || mappings.isEmpty()) {
            return sql;
        }

        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        Object paramObject = boundSql.getParameterObject();
        String preSql = sql;
        
        // 使用更精确的替换方式避免误替换
        StringBuffer result = new StringBuffer();
        Matcher matcher = REPLACE_PATTERN.matcher(sql);
        int index = 0;
        
        while (matcher.find()) {
            String replacement = "?";
            if (index < mappings.size()) {
                ParameterMapping mapping = mappings.get(index++);
                String property = mapping.getProperty();
                Object value = getParamValue(property, paramObject, boundSql, typeHandlerRegistry);
                replacement = formatValue(value);
            }
            matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
        }
        matcher.appendTail(result);
        
        return result.toString();
    }

    private Object getParamValue(String property, Object paramObject, BoundSql boundSql, 
                                TypeHandlerRegistry registry) {
        if (paramObject == null) {
            return null;
        }
        
        if (registry.hasTypeHandler(paramObject.getClass())) {
            return paramObject;
        }
        
        MetaObject metaObject = SystemMetaObject.forObject(paramObject);
        if (metaObject.hasGetter(property)) {
            return metaObject.getValue(property);
        }
        
        // 处理Map类型的参数
        if (paramObject instanceof Map) {
            return ((Map<?, ?>) paramObject).get(property);
        }
        
        // 处理额外参数
        if (boundSql.hasAdditionalParameter(property)) {
            return boundSql.getAdditionalParameter(property);
        }
        
        return null;
    }

    private String formatValue(Object value) {
        if (value == null) return "NULL";
        
        if (value instanceof String || value instanceof Date) {
            return "'" + value.toString() + "'";
        }
        if (value instanceof Iterable) {
            return formatIterable((Iterable<?>) value);
        }
        return value.toString();
    }

    private String formatIterable(Iterable<?> iterable) {
        StringBuilder sb = new StringBuilder("(");
        Iterator<?> it = iterable.iterator();
        while (it.hasNext()) {
            Object item = it.next();
            sb.append(formatValue(item));
            if (it.hasNext()) sb.append(",");
        }
        return sb.append(")").toString();
    }

    private boolean isBatchOperation(BoundSql boundSql) {
        Object paramObj = boundSql.getParameterObject();
        return paramObj instanceof Collection && boundSql.getSql().toUpperCase().contains("IN"); 
    }

    private void handleBatchOperation(Invocation invocation, StatementHandler handler, 
                                    BoundSql boundSql, String formattedSql) throws Throwable {
        Collection<?> batchParams = (Collection<?>) boundSql.getParameterObject();
        Configuration configuration = getConfiguration(handler);
        
        if (!batchParams.isEmpty()) {
            int batchCount = 0;
            for (Object param : batchParams) {
                String completeSql = formatSql(formattedSql, boundSql, configuration);
                logger.debug("批量操作[{}] SQL: {}", ++batchCount, completeSql);
                // 只打印前3条避免日志过大
                if (batchCount >= 3) break;
            }
            if (batchParams.size() > 3) {
                logger.debug("...省略批量操作中的{}条记录", batchParams.size() - 3);
            }
        }
    }

    // 获取Configuration的正确方式
    private Configuration getConfiguration(StatementHandler handler) {
        MetaObject metaObject = SystemMetaObject.forObject(handler);
        StatementHandler delegate = (StatementHandler) metaObject.getValue("delegate");
        if (delegate != null) {
            metaObject = SystemMetaObject.forObject(delegate);
        }
        return (Configuration) metaObject.getValue("configuration");
    }
    
    private MappedStatement getMappedStatement(StatementHandler handler) {
        MetaObject metaObject = SystemMetaObject.forObject(handler);
        StatementHandler delegate = (StatementHandler) metaObject.getValue("delegate");
        if (delegate != null) {
            metaObject = SystemMetaObject.forObject(delegate);
        }
        return (MappedStatement) metaObject.getValue("mappedStatement");
    }

    // 动态开关
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 从配置初始化
    }
}

配置拦截器bean注册

@Configuration
public class MyBatisConfig {


    @Bean
    public SqlPrinterInterceptor sqlPrinterInterceptor() {
        SqlPrinterInterceptor interceptor = new SqlPrinterInterceptor();
        interceptor.setEnabled(true); // 默认开启,生产环境建议默认关闭
        return interceptor;
    }


}

开启日志配置

logging:
  level:
    SQL_LOGGER: DEBUG # 控制SQL打印

执行效果

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐