SqlExplainInterceptor.java 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /**
  2. * Copyright (c) 2011-2020, hubin (jobob@qq.com).
  3. * <p>
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. * <p>
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. * <p>
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.baomidou.mybatisplus.plugins;
  17. import java.sql.Connection;
  18. import java.sql.PreparedStatement;
  19. import java.sql.ResultSet;
  20. import java.util.Properties;
  21. import org.apache.ibatis.builder.StaticSqlSource;
  22. import org.apache.ibatis.executor.Executor;
  23. import org.apache.ibatis.logging.Log;
  24. import org.apache.ibatis.logging.LogFactory;
  25. import org.apache.ibatis.mapping.BoundSql;
  26. import org.apache.ibatis.mapping.MappedStatement;
  27. import org.apache.ibatis.mapping.SqlCommandType;
  28. import org.apache.ibatis.plugin.Interceptor;
  29. import org.apache.ibatis.plugin.Intercepts;
  30. import org.apache.ibatis.plugin.Invocation;
  31. import org.apache.ibatis.plugin.Plugin;
  32. import org.apache.ibatis.plugin.Signature;
  33. import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
  34. import org.apache.ibatis.session.Configuration;
  35. import com.baomidou.mybatisplus.enums.DBType;
  36. import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
  37. import com.baomidou.mybatisplus.toolkit.GlobalConfigUtils;
  38. import com.baomidou.mybatisplus.toolkit.StringUtils;
  39. import com.baomidou.mybatisplus.toolkit.VersionUtils;
  40. /**
  41. * <p>
  42. * SQL 执行分析拦截器【 目前只支持 MYSQL-5.6.3 以上版本 】
  43. * </p>
  44. *
  45. * @author hubin
  46. * @Date 2016-08-16
  47. */
  48. @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
  49. public class SqlExplainInterceptor implements Interceptor {
  50. private static final Log logger = LogFactory.getLog(SqlExplainInterceptor.class);
  51. /**
  52. * Mysql支持分析SQL的最小版本
  53. */
  54. private final String minMySQLVersion = "5.6.3";
  55. /**
  56. * 发现执行全表 delete update 语句是否停止执行
  57. */
  58. private boolean stopProceed = false;
  59. @Override
  60. public Object intercept(Invocation invocation) throws Throwable {
  61. /**
  62. * 处理 DELETE UPDATE 语句
  63. */
  64. MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
  65. if (ms.getSqlCommandType() == SqlCommandType.DELETE || ms.getSqlCommandType() == SqlCommandType.UPDATE) {
  66. Executor executor = (Executor) invocation.getTarget();
  67. Configuration configuration = ms.getConfiguration();
  68. Object parameter = invocation.getArgs()[1];
  69. BoundSql boundSql = ms.getBoundSql(parameter);
  70. Connection connection = executor.getTransaction().getConnection();
  71. String databaseVersion = connection.getMetaData().getDatabaseProductVersion();
  72. if (GlobalConfigUtils.getDbType(configuration).equals(DBType.MYSQL)
  73. && VersionUtils.compare(minMySQLVersion, databaseVersion)) {
  74. logger.warn("Warn: Your mysql version needs to be greater than '5.6.3' to execute of Sql Explain!");
  75. return invocation.proceed();
  76. }
  77. /**
  78. * 执行 SQL 分析
  79. */
  80. sqlExplain(configuration, ms, boundSql, connection, parameter);
  81. }
  82. return invocation.proceed();
  83. }
  84. /**
  85. * <p>
  86. * 判断是否执行 SQL
  87. * </p>
  88. *
  89. * @param configuration
  90. * @param mappedStatement
  91. * @param boundSql
  92. * @param connection
  93. * @param parameter
  94. * @return
  95. * @throws Exception
  96. */
  97. protected void sqlExplain(Configuration configuration, MappedStatement mappedStatement, BoundSql boundSql,
  98. Connection connection, Object parameter) {
  99. StringBuilder explain = new StringBuilder("EXPLAIN ");
  100. explain.append(boundSql.getSql());
  101. String sqlExplain = explain.toString();
  102. StaticSqlSource sqlsource = new StaticSqlSource(configuration, sqlExplain, boundSql.getParameterMappings());
  103. MappedStatement.Builder builder = new MappedStatement.Builder(configuration, "explain_sql", sqlsource,
  104. SqlCommandType.SELECT);
  105. builder.resultMaps(mappedStatement.getResultMaps()).resultSetType(mappedStatement.getResultSetType())
  106. .statementType(mappedStatement.getStatementType());
  107. MappedStatement queryStatement = builder.build();
  108. DefaultParameterHandler handler = new DefaultParameterHandler(queryStatement, parameter, boundSql);
  109. try (PreparedStatement stmt = connection.prepareStatement(sqlExplain)) {
  110. handler.setParameters(stmt);
  111. try (ResultSet rs = stmt.executeQuery()) {
  112. while (rs.next()) {
  113. if (!"Using where".equals(rs.getString("Extra"))) {
  114. if (this.isStopProceed()) {
  115. throw new MybatisPlusException("Error: Full table operation is prohibited. SQL: " + boundSql.getSql());
  116. }
  117. break;
  118. }
  119. }
  120. }
  121. } catch (Exception e) {
  122. throw new MybatisPlusException(e);
  123. }
  124. }
  125. @Override
  126. public Object plugin(Object target) {
  127. if (target instanceof Executor) {
  128. return Plugin.wrap(target, this);
  129. }
  130. return target;
  131. }
  132. @Override
  133. public void setProperties(Properties prop) {
  134. String stopProceed = prop.getProperty("stopProceed");
  135. if (StringUtils.isNotEmpty(stopProceed)) {
  136. this.stopProceed = Boolean.valueOf(stopProceed);
  137. }
  138. }
  139. public boolean isStopProceed() {
  140. return stopProceed;
  141. }
  142. public void setStopProceed(boolean stopProceed) {
  143. this.stopProceed = stopProceed;
  144. }
  145. }