mybatis数据库拦截器实现对sql语句的打印或日志输出

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Stopwatch;
import com.sap.db.jdbc.ParseInfo;
import com.sap.db.jdbc.PreparedStatementSapDB;
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.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;

/**
 * mybatis数据库操作日志记录
 * @author chenjiwen
 */
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
        , @Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
        , @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
@Slf4j
public class DbPerformanceInterceptor implements Interceptor {

    /**
     * 如果发现对性能有影响,可以通过 spring.sls.sql.enable = false进行关闭。
     */
    @Value("${spring.sls.sql.enable:false}")
    private boolean sqlLogFlag;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 关闭了SQL开关,如果发现对性能有影响,可以通过 spring.sls.sql.enable = false进行关闭。
        if(!sqlLogFlag){
            return invocation.proceed();
        }

        Object firstArg = invocation.getArgs()[0];
        Statement statement;
        if (Proxy.isProxyClass(firstArg.getClass())) {
            statement = (Statement)SystemMetaObject.forObject(firstArg).getValue("h.statement");
        } else {
        }

        statement = (Statement)firstArg;
        MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);

        try {
            statement = (Statement)stmtMetaObj.getValue("stmt.statement");
        } catch (Exception e) {
        }

        if (stmtMetaObj.hasGetter("delegate")) {
            try {
                statement = (Statement)stmtMetaObj.getValue("delegate");
            } catch (Exception e) {
            }
        }
        String originalSql;
        String type;
        if(statement instanceof PreparedStatementSapDB){
            ParseInfo parseInfo = ((PreparedStatementSapDB) statement).getParseInfo();
            originalSql = parseInfo.getSQL();
            type ="hana数据库";
        }else{
            originalSql = statement.toString();
            type ="mysql数据库";
        }


        originalSql = originalSql.replaceAll("\\s+", " ");
        int index = indexOfSqlStart(originalSql);
        if (index > 0) {
            originalSql = originalSql.substring(index);
        }

        Stopwatch stopwatch = Stopwatch.createStarted();

        Object result = invocation.proceed();

        stopwatch.stop();
        // optional
        long executeTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);

        Object target = realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(target);
        MappedStatement ms = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
        ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
        Object parameterObject = parameterHandler.getParameterObject();
       
        // 获取到节点的id,即sql语句的id
        String sqlId = ms.getId();

        // BoundSql就是封装myBatis最终产生的sql类
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 获取节点的配置
        Configuration configuration = ms.getConfiguration();
        // 获取到最终的sql语句
        getSql(configuration, boundSql, sqlId, executeTime,type);
        resultShow(result);
        return result;
    }

    public static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }

    private int indexOfSqlStart(String sql) {
        String upperCaseSql = sql.toUpperCase();
        Set<Integer> set = new HashSet();
        set.add(upperCaseSql.indexOf("SELECT "));
        set.add(upperCaseSql.indexOf("UPDATE "));
        set.add(upperCaseSql.indexOf("INSERT "));
        set.add(upperCaseSql.indexOf("DELETE "));
        set.remove(-1);
        if (CollectionUtils.isEmpty(set)) {
            return -1;
        } else {
            List<Integer> list = new ArrayList(set);
            list.sort(Comparator.naturalOrder());
            return (Integer)list.get(0);
        }
    }

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

    @Override
    public void setProperties(Properties properties) {
    }
    /**
     * 封装sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
     * @param configuration
     * @param boundSql
     * @param sqlId
     * @param time
     */
    public static void getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time,String type) {
        String sql = showSql(configuration, boundSql);
        String sqlLogger = "\n\n============================  Sql Start  ============================" +
                "\n执行方法method :{}" +
                "\n执行SQL :{}" +
                "\n执行耗时durationTime:{} ms" +
                "\n数据库类型type :{} " +
                "\n============================  Sql  End   ============================";
        log.info(sqlLogger, sqlId, sql, time,type);
    }

    /**
     * 封装sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句

     */
    public static void resultShow(Object resultObject)  {
        if(ObjectUtil.isNotEmpty(resultObject)){
            JSONArray objects = JSONUtil.parseArray(resultObject);
            String sqlLogger = "\n============================ result Show ============================" +
                    "\n执行结果计数 :{}" +
                    "\n执行结果result :{}" +
                    "\n============================  result   End   ============================\n";
            log.info(sqlLogger,objects.size(), JSONUtil.toJsonStr(resultObject));
        }

    }

    /**
     * 如果参数是String,则添加单引号;
     * 如果是日期,则转换为时间格式器并加单引号;
     @param obj
     @return
     */
    private static String getParameterValue(Object obj) {
        String value;
        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;
    }

    /**
     * 对sql中的?进行参数替换
     * @param configuration
     * @param boundSql
     * @return
     */
    public static String showSql(Configuration configuration, BoundSql boundSql) {
        // 获取参数
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql
                .getParameterMappings();
        // sql语句中多个空格都用一个空格代替
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (!CollectionUtils.isEmpty(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("\\?", "missing");
                    }
                }
            }
        }
        return sql;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值