拦截器介绍
MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2. ParameterHandler (getParameterObject, setParameters)
3. ResultSetHandler (handleResultSets, handleOutputParameters)
4. StatementHandler (prepare, parameterize, batch, update, query)
我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。
总体概括为:
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截Sql语法构建的处理
拦截器使用
拦截sql并增强
import com.cq.controller.InterceptAnnotation;
import lombok.extern.log4j.Log4j2;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Properties;
/**
* sql拦截器,通过mybatis提供的Interceptor接口实现
*/
@Log4j2
@Component
//拦截StatementHandler类中参数类型为Statement的prepare方法(prepare=在预编译SQL前加入修改的逻辑)
//即拦截 Statement prepare(Connection var1, Integer var2) 方法
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MySqlInterceptor implements Interceptor {
/**
* 拦截sql
*
* @param invocation
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
// 优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
// 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// id为执行的mapper方法的全路径名,如com.cq.UserMapper.insertUser, 便于后续使用反射
String id = mappedStatement.getId();
// sql语句类型 select、delete、insert、update
String sqlCommandType = mappedStatement.getSqlCommandType().toString();
// 数据库连接信息
// Configuration configuration = mappedStatement.getConfiguration();
// ComboPooledDataSource dataSource = (ComboPooledDataSource)configuration.getEnvironment().getDataSource();
// dataSource.getJdbcUrl();
BoundSql boundSql = statementHandler.getBoundSql();
// 获取到原始sql语句
String sql = boundSql.getSql().toLowerCase();
log.info("SQL:{}", sql);
// 增强sql
// 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
String mSql = sqlAnnotationEnhance(id, sqlCommandType, sql);
// 直接增强sql
// mSql = sql + " limit 2";
//通过反射修改sql语句
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, mSql);
log.info("增强后的SQL:{}", mSql); // 打印:增强后的SQL:select * from scenario_storage limit 2
return invocation.proceed();
}
/**
* 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
* @param id 方法全路径
* @param sqlCommandType sql类型
* @param sql 所执行的sql语句
*/
private String sqlAnnotationEnhance(String id, String sqlCommandType, String sql) throws ClassNotFoundException {
// 通过类全路径获取Class对象
Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));
// 获取当前所拦截的方法名称
String mName = id.substring(id.lastIndexOf(".") + 1);
// 遍历类中所有方法名称,并if匹配上当前所拦截的方法
for (Method method : classType.getDeclaredMethods()) {
if (mName.equals(method.getName())) {
// 判断方法上是否带有自定义@InterceptAnnotation注解
InterceptAnnotation interceptorAnnotation = method.getAnnotation(InterceptAnnotation.class);
if (interceptorAnnotation.flag()) {
if ("SELECT".equals(sqlCommandType)) {
// 增强sql
return sql + " limit 2";
// select * from scenario_storage limit 2
}
}
}
}
return sql;
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
}
测试mapper接口
public interface IntercaperMapper {
@InterceptAnnotation
@Select("select * from scenario_storage")
List<ScenarioStorageEntity> queryScenarioStorageAll();
}
实体类
import lombok.Data;
@Data
public class ScenarioStorageEntity {
private int id;
private String name;
}
自定义sql增强注解
/**
* sql增强注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterceptAnnotation {
String value() default "";
/** true增强、false忽略 */
boolean flag() default true;
}
实践:用户只能看到自己相关的数据(通过mysql拦截器实现)
🍎原创不易,感觉对自己有用的话就❤️点赞👉收藏💬评论把。