31. 如何在MyBatis中使用自定义拦截器?有哪些常见应用场景?
大约 4 分钟
在 MyBatis 中,自定义拦截器是一个强大的功能,允许开发者在 SQL 语句执行的不同阶段进行拦截、修改或扩展操作。拦截器可以用于日志记录、性能监控、权限检查等多种场景。MyBatis 提供了 Interceptor
接口来实现自定义拦截器。
如何在 MyBatis 中使用自定义拦截器?
1. 实现 Interceptor
接口
要创建一个自定义拦截器,首先需要实现 MyBatis 的 Interceptor
接口。这个接口有一个 intercept
方法,负责处理拦截的逻辑。
import org.apache.ibatis.executor.statement.StatementHandler;
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 java.sql.Connection;
import java.util.Properties;
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyCustomInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 在这里添加拦截逻辑
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
String sql = statementHandler.getBoundSql().getSql();
System.out.println("Executing SQL: " + sql);
// 调用原方法
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置拦截器的属性
}
}
2. 配置拦截器
实现拦截器之后,需要在 MyBatis 配置文件中注册这个拦截器。
XML 配置:
<configuration>
<plugins>
<plugin interceptor="com.example.MyCustomInterceptor">
<!-- 可以在这里传递自定义属性 -->
<property name="property1" value="value1"/>
</plugin>
</plugins>
</configuration>
Java 配置(如果使用 Spring):
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 注册自定义拦截器
MyCustomInterceptor interceptor = new MyCustomInterceptor();
sessionFactory.setPlugins(new Interceptor[]{interceptor});
return sessionFactory.getObject();
}
3. 使用拦截器
在你配置了拦截器后,MyBatis 会在执行 SQL 语句的过程中自动调用拦截器。根据拦截器的配置,它会在不同的执行阶段进行干预,例如 SQL 语句准备阶段、执行阶段、结果处理阶段等。
常见应用场景
自定义拦截器在 MyBatis 中有广泛的应用场景,以下是几个常见的应用:
1. SQL 日志记录
可以使用拦截器记录每次执行的 SQL 语句以及执行时间。这对于性能调优和问题排查非常有帮助。
示例:
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlLoggerInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
String sql = statementHandler.getBoundSql().getSql();
try {
return invocation.proceed();
} finally {
long endTime = System.currentTimeMillis();
System.out.println("SQL: " + sql + " executed in " + (endTime - startTime) + " ms");
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
2. 数据权限控制
在一些应用中,可能需要在查询或更新数据时,基于用户角色或权限对 SQL 进行过滤或修改。例如,限制某些用户只能查询自己负责的记录。
示例:
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
String sql = statementHandler.getBoundSql().getSql();
// 基于某些条件修改 SQL,比如添加 where 条件限制
if (someCondition()) {
sql = modifySqlForPermission(sql);
Field sqlField = statementHandler.getBoundSql().getClass().getDeclaredField("sql");
sqlField.setAccessible(true);
sqlField.set(statementHandler.getBoundSql(), sql);
}
return invocation.proceed();
}
private boolean someCondition() {
// 检查权限或角色
return true;
}
private String modifySqlForPermission(String sql) {
// 修改 SQL 以加入权限限制
return sql + " AND user_id = 123";
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
3. 分页处理
在实现数据库分页功能时,可以通过拦截器自动在 SQL 语句中添加 LIMIT
和 OFFSET
子句,避免手动在每个查询中添加分页逻辑。
示例:
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PaginationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
String sql = statementHandler.getBoundSql().getSql();
// 添加分页参数
int offset = 0;
int limit = 10;
sql = sql + " LIMIT " + offset + "," + limit;
Field sqlField = statementHandler.getBoundSql().getClass().getDeclaredField("sql");
sqlField.setAccessible(true);
sqlField.set(statementHandler.getBoundSql(), sql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
4. 性能监控
拦截器可以用于收集和记录 SQL 执行的时间、次数等信息,帮助进行性能分析和优化。
示例:
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PerformanceMonitoringInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.nanoTime();
Object result = invocation.proceed();
long endTime = System.nanoTime();
System.out.println("SQL executed in " + (endTime - startTime) / 1_000_000 + " ms");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
总结
- 自定义拦截器:在 MyBatis 中,自定义拦截器通过实现
Interceptor
接口来定义拦截逻辑,并通过@Intercepts
注解来指定要拦截的目标方法。 - 常见应用场景:SQL 日志记录、数据权限控制、分页处理、性能监控等。
- 配置拦截器:可以在 MyBatis 配置文件中或通过 Java 代码配置自定义拦截器,以实现对 SQL 执行过程的干预。
通过自定义拦截器,可以在 MyBatis 执行 SQL 的各个阶段插入自定义逻辑,从而实现对数据库操作的灵活控制和扩展。