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 = ?
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);
}
}