使用MyBatis 拦截器的作用
MyBatis 拦截器主要用于在MyBatis的SQL执行过程中插入自定义的行为或逻辑,无需直接修改MyBatis框架本身的代码。拦截器的设计目的是为了让开发者能够在不影响MyBatis核心功能的基础上,根据自身需求定制化特定的执行流程。
MyBatis的拦截器主要可以应用于以下几个方面:
-
增强或修改SQL:例如,可以根据运行时条件动态修改SQL语句,如添加额外的where条件,或者动态改变表名。
-
性能监控:可以用来记录SQL执行耗时、收集SQL统计信息,以便于对数据库操作进行性能分析。
-
权限控制:在执行SQL前检查用户权限,根据权限决定是否执行或修改SQL。
-
参数处理:在参数传递给SQL之前进行特殊处理,比如对敏感数据进行加密或解密,或者统一转换日期格式等。
-
结果集处理:在查询结果返回给客户端前进行加工,比如字段过滤、数据脱敏、结果合并等。
-
异常处理:统一处理数据库操作异常,提供统一的错误提示或者日志记录。
Mybatis提供了四种类型的拦截器
1. Executor(执行器拦截器):
- 用途:拦截MyBatis执行器方法的执行。
- 使用:允许拦截和自定义MyBatis执行器的行为。例如,可以添加缓存、日志记录或审计功能到执行器中。这些拦截器可以在MyBatis执行的不同阶段扩展或修改其行为。您可以通过实现MyBatis提供的相应接口并在MyBatis配置文件中进行配置来实现这些拦截器。
2. StatementHandler(语句拦截器):
-- 用途:拦截SQL语句的执行。
- 使用:可以在SQL语句执行之前修改或增强它们。例如,可以向WHERE子句添加额外的条件或记录执行的语句。分页等
3. ParameterHandler(参数拦截器):
- 用途:拦截SQL语句的参数设置。
- 使用:允许在将参数设置到SQL语句之前修改或验证它们。例如,可以对作为参数传递的敏感信息进行加密或解密。
4. ResultHandler(结果集拦截器):
- 用途:拦截从SQL语句返回的结果集的处理。
- 使用:可以在将结果集返回给应用程序之前修改或分析它们。例如,可以对结果集数据进行转换或执行额外的计算。
拦截的执行顺序是Executor->StatementHandler->ParameterHandler->ResultHandler
MyBatis
拦截器用到了两个注解:@Intercepts
和@Signature
- @Intercepts:它用于标识当前的对象是一个拦截器,其配置值是一个@Signature的集合。
- @Signature:它用于标识需要拦截的接口、方法、对应的参数列表。
四种类型的拦截器的实现
@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}),
}
)
type
的值与类名相同,method
与方法名相同,为了避免方法重载,args
中指定了各个参数的类型和个数,可通过invocation.getArgs()
获取参数数组。
Executor 拦截器实现
@Intercepts({@Signature(
type= Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
@Slf4j
@Component
public class ExecutorInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
String sql = ExecutorPluginUtils.getSqlByInvocation(invocation);
//可以对sql重写
log.error("拦截器ExecutorInterceptor:"+sql);
//sql = "SELECT id from BUS_RECEIVER where id = ? ";
ExecutorPluginUtils.resetSql2Invocation( invocation, sql);
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
}
}
ParameterHandler 拦截器实现
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@Component
@Slf4j
public class ParamInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.error("拦截器ParamInterceptor");
//拦截 ParameterHandler 的 setParameters 方法 动态设置参数
if (invocation.getTarget() instanceof ParameterHandler) {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
// 反射获取 BoundSql 对象,此对象包含生成的sql和sql的参数map映射
Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");
boundSqlField.setAccessible(true);
BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);
// 反射获取 参数对像
Field parameterField =
parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject instanceof Map) {
//将参数中的name值改为2
((Map) parameterObject).put("name","2");
}
// 改写的参数设置到原parameterHandler对象
parameterField.set(parameterHandler, parameterObject);
parameterHandler.setParameters(ps);
log.error(JSON.toJSONString(boundSql.getParameterMappings()));
log.error(JSON.toJSONString(parameterObject));
}
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
}
ResultSetHandler 拦截器实现
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args={
Statement.class})
})
@Component
@Slf4j
public class ResultInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.error("拦截器ResultInterceptor");
// ResultSetHandler resultSetHandler1 = (ResultSetHandler) invocation.getTarget();
//通过java反射获得mappedStatement属性值
//可以获得mybatis里的resultype
Object result = invocation.proceed();
if (result instanceof ArrayList) {
ArrayList resultList = (ArrayList) result;
for (int i = 0; i < resultList.size(); i++) {
Object oi = resultList.get(i);
Class c = oi.getClass();
Class[] types = {
String.class};
Method method = c.getMethod("setAddress", types);
// 调用obj对象的 method 方法
method.invoke(oi, "china");
}
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
StatementHandler 拦截器实现
@Intercepts(
{
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {
Connection.class, Integer.class}
)
})
@Component
@Slf4j
public class StatementInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler =
(StatementHandler) PluginUtils.realTarget(invocation.getTarget());
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement =
(MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
//只拦截select方法
if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
return invocation.proceed();
}
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
//获取到sql
String originalSql = boundSql.getSql();
//可以对originalSql进行改写
log.error("拦截器StatementInterceptor:"+originalSql);
metaStatementHandler.setValue("delegate.boundSql.sql", originalSql);
Object parameterObject = boundSql.getParameterObject();
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}