Mybatis custom Explain plugin

Mybatis custom Explain plugin

Scenes:

Recently, there is a demand: create a joint index of a table, and let the query on the table go to this joint index

Purpose:

Every time a sql query is executed, each sql can be intercepted, and its performance can be analyzed through explain. Check whether its Extra attribute is Using index, and whether its key attribute is a newly created index.

Example:
Search province according to the name of the province. Explain analyzes whether the index is used. and went to the ProName index

EXPLAIN select distinct
    ProID
    ,ProName
    from province p
     WHERE p.ProName = ?

insert image description here

accomplish

Customize the impl class to implement the Interceptor interface

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
@Component
@Intercepts({
    
    
        @Signature(
                type = Executor.class,
                method = "query",
                args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
                }
        )
})
public class ExplainInterceptor implements Interceptor {
    
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        if (ms.getSqlCommandType() == SqlCommandType.SELECT) {
    
    
            Executor executor = (Executor) invocation.getTarget();
            Configuration configuration = ms.getConfiguration();
            Object parameter = invocation.getArgs()[1];
            BoundSql boundSql = ms.getBoundSql(parameter);
            Connection connection = executor.getTransaction().getConnection();
            sqlExplain(configuration, ms, boundSql, connection, parameter);
        }

        Object result = invocation.proceed();//target对象应执行的方法
        return result;
    }

    private void sqlExplain(Configuration configuration, MappedStatement mappedStatement, BoundSql boundSql, Connection connection, Object parameter) {
    
    
        // 这里注意:EXPLAIN后面必须要有空格,否则sql为: explainselect报错
        StringBuilder explain = new StringBuilder("EXPLAIN ");
        String sqlExplain = explain.append(boundSql.getSql()).toString();
        System.out.println("============================================");
        System.out.println(sqlExplain);
        System.out.println("============================================");
        StaticSqlSource sqlSource = new StaticSqlSource(configuration, sqlExplain, boundSql.getParameterMappings());
        MappedStatement.Builder builder = new MappedStatement.Builder(configuration, "explain_sql", sqlSource, SqlCommandType.SELECT);
        MappedStatement queryStatement = builder.build();
        builder.resultMaps(mappedStatement.getResultMaps()).resultSetType(mappedStatement.getResultSetType())
                .statementType(mappedStatement.getStatementType());
        DefaultParameterHandler handler = new DefaultParameterHandler(queryStatement, parameter, boundSql);
        try {
    
    
            PreparedStatement stmt = connection.prepareStatement(sqlExplain);
            handler.setParameters(stmt);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()){
    
    
                String extra = rs.getString("Extra");
                int index = extra.indexOf("Using index");
                //判断,是否走了索引。还是走的Using where
                if (index == -1){
    
    
                    throw new MybatisPlusException("Error:Full table operator is prohibited. SQL:"+boundSql.getSql());
                }
                //判断,是否走到索引idx_ProName上 
                if (!"idx_ProName".equals(rs.getString("key"))){
    
    
                    throw new MybatisPlusException("Error:idx is not used. SQL:"+boundSql.getSql());
                }
            }
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }

    }

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

    @Override
    public void setProperties(Properties properties) {
    
    
        Interceptor.super.setProperties(properties);
    }
}

Guess you like

Origin blog.csdn.net/tmax52HZ/article/details/119560023