SpringBoot解析MyBatis预编译SQL

pom.xml

java 复制代码
		<profile>
            <!-- 开发环境 -->
            <id>dev</id>
            <activation>
                <!--  默认激活 -->
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <spring.profiles.active>dev</spring.profiles.active>
            </properties>
        </profile>

application.yml

yaml 复制代码
# 开启 MyBatis SQL 日志解析
mybatis-log:
  # 默认:false
  enabled: true

MyBatisSqlParsingPlugin.java

默认是dev开启彩色日志

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class,}),
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
})
@Component
@ConditionalOnProperty(prefix = "mybatis-log", value = "enabled", havingValue = "true")
public final class MyBatisSqlParsingPlugin implements Interceptor {
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Resource
    private Environment env;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        ParameterHandler parameterHandler = statementHandler.getParameterHandler();
        BoundSql boundSql = statementHandler.getBoundSql();
        String[] activeProfiles = env.getActiveProfiles();
        String activeProfile = "";
        if (activeProfiles.length > 0) {
            activeProfile = activeProfiles[0];
        }
        try {
            String sql = formatSql(parameterHandler, boundSql);
            if (!boundSql.getSql().equals(sql)) {
                if (StringUtils.hasText(activeProfile) && "dev".equals(activeProfile)) {
                    log.info("Execute SQL:\033[32m{}\033[0m", sql);
                } else {
                    log.info("Execute SQL:{}", sql);
                }
            }
        } catch (Exception e) {
            String sql = boundSql.getSql();
            if (StringUtils.hasText(activeProfile) && "dev".equals(activeProfile)) {
                log.error("Execute SQL:\033[31m{}\033[0m \nException:", sql, e);
            } else {
                log.error("Execute SQL:{}\nException:", sql, e);
            }
        }
        return invocation.proceed();
    }

    /**
     * 格式化SQL及其参数
     */
    private String formatSql(ParameterHandler parameterHandler, BoundSql boundSql) throws NoSuchFieldException, IllegalAccessException {

        Class<? extends ParameterHandler> parameterHandlerClass = parameterHandler.getClass();
        Field mappedStatementField = parameterHandlerClass.getDeclaredField("mappedStatement");
        mappedStatementField.setAccessible(true);
        MappedStatement mappedStatement = (MappedStatement) mappedStatementField.get(parameterHandler);

        String sql = boundSql.getSql().replaceAll("\\s+", " ");

        // sql字符串是空或存储过程,直接跳过
        if (!StringUtils.hasText(sql) || sql.trim().charAt(0) == '{') {
            return "";
        }

        // 不传参数的场景,直接把Sql美化一下返回出去
        Object parameterObject = parameterHandler.getParameterObject();
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
        if (Objects.isNull(parameterObject) || parameterMappingList.isEmpty()) {
            return sql;
        }

        return handleCommonParameter(boundSql, mappedStatement);
    }

    //替换预编译SQL
    private String handleCommonParameter(BoundSql boundSql, MappedStatement mappedStatement) {
        String sql = boundSql.getSql();
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Configuration configuration = mappedStatement.getConfiguration();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        List<String> params = new ArrayList<>();

        for (ParameterMapping parameterMapping : parameterMappings) {
            if (parameterMapping.getMode() == ParameterMode.OUT) {
                continue;
            }
            Object propertyValue;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
                propertyValue = boundSql.getAdditionalParameter(propertyName);
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                propertyValue = parameterObject;
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                propertyValue = metaObject.getValue(propertyName);
            }
            params.add(this.formatParam(propertyValue));
        }

        //转译百分号
        if (sql.contains("%")) {
            //如果参数不一致直接返回SQL
            Pattern pattern = Pattern.compile("\\?");
            Matcher matcher = pattern.matcher(sql);
            int count = 0;
            while (matcher.find()) {
                count++;
            }
            if (count == 0 || params.isEmpty()) {
                return sql;
            }
            if (params.size() != count) {
                log.error("SQL:{}", sql);
                log.error("SQL parameters:{}", params);
                return sql;
            }
            sql = sql.replaceAll("%", "%%");
        }

        sql = sql.replaceAll("\\?", "%s");
        return String.format(sql, params.toArray());
    }

    private String formatParam(Object object) {
        if (object == null) {
            return "null";
        }
        if (object instanceof String) {
            return formatString((String) object);
        }
        if (object instanceof Date) {
            return formatDate((Date) object);
        }
        if (object instanceof LocalDate) {
            return formatLocalDate((LocalDate) object);
        }
        if (object instanceof LocalDateTime) {
            return formatLocalDateTime((LocalDateTime) object);
        }
        return object.toString();
    }

    private static String formatString(String str) {
        return "'" + str + "'";
    }

    private String formatDate(Date date) {
        return "'" + DATE_TIME_FORMATTER.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()) + "'";
    }

    private String formatLocalDate(LocalDate date) {
        return "'" + DATE_FORMATTER.format(date) + "'";
    }

    private String formatLocalDateTime(LocalDateTime dateTime) {
        return "'" + DATE_TIME_FORMATTER.format(dateTime) + "'";
    }
}
相关推荐
IT毕设梦工厂1 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
是梦终空2 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
知识分享小能手3 小时前
mysql学习教程,从入门到精通,SQL DISTINCT 子句 (16)
大数据·开发语言·sql·学习·mysql·数据分析·数据库开发
工业互联网专业3 小时前
毕业设计选题:基于springboot+vue+uniapp的驾校报名小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
无名指的等待7124 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
Tatakai254 小时前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis
.生产的驴4 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
A_cot5 小时前
Redis 的三个并发问题及解决方案(面试题)
java·开发语言·数据库·redis·mybatis
AskHarries5 小时前
Spring Boot利用dag加速Spring beans初始化
java·spring boot·后端
苹果酱05675 小时前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件