All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.dreamroute.sqlprinter.starter.interceptor.SqlPrinter Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 [email protected]
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.github.dreamroute.sqlprinter.starter.interceptor;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.sql.SQLUtils;
import com.github.dreamroute.sqlprinter.starter.anno.SqlprinterProperties;
import com.github.dreamroute.sqlprinter.starter.anno.ValueConverter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.*;
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.SqlSessionFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.*;

import static java.util.Optional.ofNullable;

/**
 * print simple sql
 *
 * @author [email protected]
 * @version 1.0
 * @since JDK1.8
 */
@Slf4j
@EnableConfigurationProperties(SqlprinterProperties.class)
@Intercepts(
        {
                @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}),
                @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
        }
)
public class SqlPrinter implements Interceptor, ApplicationListener {

    private final SqlprinterProperties sqlprinterProperties;
    private final List converters;
    private final Set filter;
    private final boolean show;

    private Configuration config;

    public SqlPrinter(SqlprinterProperties props, List converters) {
        this.sqlprinterProperties = props;
        this.converters = converters;
        filter = new HashSet<>(Arrays.asList(ofNullable(props.getFilter()).orElseGet(() -> new String[0])));
        this.show = props.isShow();
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        SqlSessionFactory sqlSessionFactory = event.getApplicationContext().getBean(SqlSessionFactory.class);
        this.config = sqlSessionFactory.getConfiguration();
    }

    @Override
    public Object intercept(Invocation invocation) throws Exception {

        // 调用原始方法
        Object result = invocation.proceed();

        try {
            Object target = invocation.getTarget();
            Parse p = new Parse();
            // 非查询走这里(非查询不走ResultSetHandler接口,所以放在这里)
            if (target instanceof ParameterHandler) {
                MetaObject m = config.newMetaObject(target);
                MappedStatement ms = (MappedStatement) m.getValue("mappedStatement");
                SqlCommandType sqlCommandType = ms.getSqlCommandType();
                if (sqlCommandType != SqlCommandType.SELECT) {
                    p.id = ms.getId();
                    extracted(ms.getId(), ms.getBoundSql(m.getValue("parameterObject")), p);
                }
            }

            // 查询走这里
            else {
                p = getSql(invocation);
            }

            if (CharSequenceUtil.isNotBlank(p.sql)) {
                printResult(result, p);
            }
        } catch (Exception e) {
            // print sql is not important, so ignore it.
        }

        return result;
    }

    private Parse getSql(Invocation invocation) {

        Parse parse = new Parse();

        MetaObject resultSetHandler = config.newMetaObject(invocation.getTarget());
        MappedStatement mappedStatement = (MappedStatement) resultSetHandler.getValue("mappedStatement");
        String id = mappedStatement.getId();
        parse.id = id;
        BoundSql boundSql = (BoundSql) resultSetHandler.getValue("boundSql");
        extracted(id, boundSql, parse);
        return parse;
    }

    private void extracted(String id, BoundSql boundSql, Parse parse) {
        if (show && !filter.contains(id)) {
            
            Object parameterObject = boundSql.getParameterObject();
            String originalSql = boundSql.getSql();
            StringBuilder sb = new StringBuilder(originalSql);
            List parameterMappings = boundSql.getParameterMappings();
            if (parameterMappings != null) {
                for (ParameterMapping parameterMapping : parameterMappings) {
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        Object value;
                        String propertyName = parameterMapping.getProperty();
                        if (boundSql.hasAdditionalParameter(propertyName)) {
                            value = boundSql.getAdditionalParameter(propertyName);
                        } else if (parameterObject == null) {
                            value = null;
                        } else if (config.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
                            value = parameterObject;
                        } else {
                            MetaObject metaObject = config.newMetaObject(parameterObject);
                            value = metaObject.getValue(propertyName);
                        }

                        // 转换器
                        for (ValueConverter vc : converters) {
                            value = vc.convert(value);
                        }

                        // sql中非数字类型的值加单引号
                        if (value != null && !(value instanceof Number)) {
                            value = "'" + value + "'";
                        }

                        // 替换问号
                        int pos = sb.indexOf("?");
                        sb.replace(pos, pos + 1, String.valueOf(value));
                    }
                }

                String info = sb.toString();
                if (sqlprinterProperties.isFormat()) {
                    info = format(info);
                }

                parse.sql = info;
            }
        }
    }

    private void printResult(Object result, Parse parse) {
        String[] columnNames = null;
        List data = null;
        if (result instanceof List && CollUtil.isNotEmpty((Collection) result)) {
            Field[] fields = ReflectUtil.getFields(((List) result).get(0).getClass());
            if (fields != null && fields.length > 0) {
                columnNames = generateColumnNames(fields);
                data = new ArrayList<>(((List) result).size());
                for (int i = 0; i < ((List) result).size(); i++) {
                    String[] d = new String[fields.length];
                    for (int j = 0; j < fields.length; j++) {
                        Object v = ReflectUtil.getFieldValue(((List) result).get(i), fields[j]);
                        d[j] = StrUtil.toString(v);
                    }
                    data.add(d);
                }
            }
        } else {
            if (result != null) {
                Field[] fields = ReflectUtil.getFields(result.getClass());
                columnNames = generateColumnNames(fields);
                data = new ArrayList<>(1);
                for (int i = 0; i < fields.length; i++) {
                    String[] d = new String[fields.length];
                    Object v = ReflectUtil.getFieldValue(result, fields[i]);
                    d[i] = StrUtil.toString(v);
                    data.add(d);
                }
            }
        }

        String resp = "\r\n\r\n";

        resp += "==> " + parse.id + "\r\n";

        resp += parse.sql + "\r\n";

        if (columnNames != null && columnNames.length > 0 && CollUtil.isNotEmpty(data)) {
            PrettyTable table = new PrettyTable(columnNames);
            data.forEach(table::addRow);
            resp += table;
        }
        log.info(resp);
    }

    private static String[] generateColumnNames(Field[] fields) {
        String[] columnNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            boolean accessible = fields[i].isAccessible();
            fields[i].setAccessible(true);
            columnNames[i] = fields[i].getName();
            fields[i].setAccessible(accessible);
        }
        return columnNames;
    }

    /**
     * 使用druid格式化sql,如果格式化失败,那么返回未经过格式化的sql,增加此格式化的原因是因为:
     * 1. 为了美观和打印的格式统一
     * 2. mysql的xml文件编写的sql带有动态标签,如果动态标签不满足条件时sql会有很多多余的换行和缩进
     *
     * @param sql 需要格式化的sql
     * @return 返回格式化之后的sql
     */
    private String format(String sql) {
        try {
            // 此格式化在不改变sql语义的情况下会移除一些括号,比如:
            // 格式化前:select * from xx where id = (#{id} and name = #{name}) and pwd = #{pwd}
            // 格式化后:select * from xx where id = #{id} and name = #{name} and pwd = #{pwd}(括号被移除)
            // 但是如果移除括号会改变sql语义,那就不会被移除
            return SQLUtils.formatMySql(sql);
        } catch (Exception e) {
            return sql;
        }
    }

    private static class Parse {
        String sql;
        String id;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy