Helius
2021-06-29 5252d1396e21a16774be699a5ba1c8d39c14a22e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package com.xzx.gc.interceptor;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.xzx.gc.common.dto.log.SqlDto;
import com.xzx.gc.common.utils.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
 
/**ALL < index < range ~ index_merge < ref < eq_ref < const < system
 * 1.可以用来分析SQL执行效率
 * 2.可以用来获取实际执行的SQL
 *  * @author zhanjixun
 * @time 2018年6月28日 19:20:23
 */
@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}
)
@Component
public class SqlInterceptor implements Interceptor {
 
    public static  final String SQL_PARAM="sqlParam";
 
    @Autowired
    private SpringUtil springUtil;
    
    private int MIN_SIZE = 0;
 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        HttpServletRequest request = springUtil.getCurrentRequest();
        List<SqlDto> sqlList=null;
        if(request!=null) {
            sqlList = (List<SqlDto>) request.getAttribute(SQL_PARAM);
        }
 
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        BoundSql boundSql=null;
        if (args.length > 5) {
            boundSql = (BoundSql) args[5];
        }
        if(boundSql==null){
            Object parameter= args[1];
            boundSql = mappedStatement.getBoundSql(parameter);
        }
        String sqlId = mappedStatement.getId();
        Configuration configuration = mappedStatement.getConfiguration();
 
        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = invocation.proceed();
        } finally {
            try {
                if(request!=null) {
                    SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
                    int effectNum = 0;
 
                    if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.DELETE) {
                        effectNum = Convert.toInt(result, 0);
                    }else if(sqlId.endsWith("_COUNT")){
                        List<?> objects = Convert.toList(result);
                        if(CollUtil.isNotEmpty(objects)){
                            effectNum=Convert.toInt(objects.get(0),0);
                        }
                    }else if(sqlCommandType==SqlCommandType.SELECT){
                        List<?> objects = Convert.toList(result);
                        if(CollUtil.isNotEmpty(objects)){
                            effectNum=objects.size();
                        }
                    }
                    long sqlCostTime = System.currentTimeMillis() - startTime;
                    String sql = getSql(configuration, boundSql);
                    SqlDto sqlDto = new SqlDto();
                    sqlDto.setSql(sql);
                    sqlDto.setSqlId(sqlId);
                    sqlDto.setCostTime(sqlCostTime + "");
                    sqlDto.setEffectNum(effectNum + "");
                    sqlDto.setSqlType(sqlCommandType.name());
                    if (CollUtil.isNotEmpty(sqlList)) {
                        sqlList.add(sqlDto);
                    } else {
                        sqlList = CollUtil.newArrayList(sqlDto);
                    }
                    request.setAttribute(SQL_PARAM, sqlList);
                }
            } catch (Exception ignored) {
//                ignored.printStackTrace();
            }
        }
        return result;
    }
 
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
 
    @Override
    public void setProperties(Properties properties) {
        if (properties == null) {
            return;
        }
        if (properties.containsKey("minLogSize")) {
            MIN_SIZE = Integer.valueOf(properties.getProperty("minLogSize"));
        }
    }
 
    private String getSql(Configuration configuration, BoundSql boundSql) {
        // 输入sql字符串空判断
        if(boundSql==null){
            return "";
        }
        String sql = boundSql.getSql();
        if (StringUtils.isBlank(sql)) {
            return "";
        }
 
        //美化sql
        sql = beautifySql(sql);
 
        //填充占位符, 目前基本不用mybatis存储过程调用,故此处不做考虑
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (!parameterMappings.isEmpty() && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = this.replacePlaceholder(sql, parameterObject);
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = replacePlaceholder(sql, obj);
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = replacePlaceholder(sql, obj);
                    }
                }
            }
        }
        return sql;
    }
 
    private String beautifySql(String sql) {
        return sql.replaceAll("[\\s\n ]+", " ");
    }
 
    private String replacePlaceholder(String sql, Object parameterObject) {
        String result;
        if(parameterObject!=null) {
            if (parameterObject instanceof String) {
                result = "'" + parameterObject.toString() + "'";
            } else if (parameterObject instanceof Date) {
                result = "'" + getDate2String((Date) parameterObject) + "'";
            } else {
                result = parameterObject.toString();
            }
            return sql.replaceFirst("\\?", result);
        }else {
            return sql;
        }
    }
 
    private String getDate2String(Date parameterObject) {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parameterObject);
    }
 
}