MyBatis Plus InnerInterceptor 深度解析:自定义拦截器实战

MyBatis Plus 是 MyBatis 的增强工具,提供了许多便捷的功能来简化开发,其中插件机制是其重要的扩展点之一。InnerInterceptor 是 MyBatis Plus 3.x 中用于实现插件拦截的一个核心接口,允许开发者自定义拦截逻辑,比如修改 SQL、设置参数或处理结果集。

图片[1]_MyBatis Plus InnerInterceptor 深度解析:自定义拦截器实战_知途无界

以下是关于 InnerInterceptor 的实现和使用方式的详细讲解。


一、InnerInterceptor 简介

InnerInterceptor 是 MyBatis Plus 提供的一个拦截器接口,主要用于在 SQL 执行的不同阶段插入自定义逻辑。与 MyBatis 的 Interceptor 类似,但更贴合 MyBatis Plus 的设计理念,通常用于分页、多租户、动态表名等场景。

常见的 InnerInterceptor 实现包括:

  • 分页插件(PaginationInnerInterceptor
  • 多租户插件(TenantLineInnerInterceptor
  • 动态表名插件(DynamicTableNameInnerInterceptor

二、InnerInterceptor 的核心方法

InnerInterceptor 接口定义了以下核心方法:

public interface InnerInterceptor {
/**
* 在执行 SQL 之前拦截
*/
boolean beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout);
/**
* 在执行查询之前拦截(针对 SELECT 语句)
*/
void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
/**
* 在更新操作(INSERT/UPDATE/DELETE)之前拦截
*/
void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException;
/**
* 在 SQL 执行完成后拦截
*/
void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql, List<Object> resultList) throws SQLException;
/**
* 清理资源(可选实现)
*/
void clearLocalCache();
}
public interface InnerInterceptor {
    /**
     * 在执行 SQL 之前拦截
     */
    boolean beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout);

    /**
     * 在执行查询之前拦截(针对 SELECT 语句)
     */
    void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                     ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

    /**
     * 在更新操作(INSERT/UPDATE/DELETE)之前拦截
     */
    void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException;

    /**
     * 在 SQL 执行完成后拦截
     */
    void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                    ResultHandler resultHandler, BoundSql boundSql, List<Object> resultList) throws SQLException;

    /**
     * 清理资源(可选实现)
     */
    void clearLocalCache();
}
public interface InnerInterceptor { /** * 在执行 SQL 之前拦截 */ boolean beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout); /** * 在执行查询之前拦截(针对 SELECT 语句) */ void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; /** * 在更新操作(INSERT/UPDATE/DELETE)之前拦截 */ void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException; /** * 在 SQL 执行完成后拦截 */ void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, List<Object> resultList) throws SQLException; /** * 清理资源(可选实现) */ void clearLocalCache(); }

开发者可以根据需求实现其中的方法,在 SQL 执行的各个阶段插入自定义逻辑。


三、自定义 InnerInterceptor 实现

以下是一个简单的自定义 InnerInterceptor 示例,用于记录 SQL 执行时间:

import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class SqlExecutionTimeInterceptor implements InnerInterceptor {
@Override
public boolean beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
long startTime = System.currentTimeMillis();
// 可以通过 ThreadLocal 或其他方式存储开始时间
System.out.println("SQL Preparation Start...");
// 模拟在 StatementHandler 中设置属性(可选)
return true; // 返回 true 表示继续执行,false 表示中断
}
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
System.out.println("Executing Query: " + boundSql.getSql());
}
@Override
public void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql, List<Object> resultList) throws SQLException {
long endTime = System.currentTimeMillis();
System.out.println("SQL Execution Time: " + (endTime - (System.currentTimeMillis() - /* 假设获取开始时间 */ 1000)) + "ms");
// 注意:实际实现中需要合理管理开始时间(如使用 ThreadLocal)
}
// 其他方法可以留空或根据需求实现
@Override
public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {}
@Override
public void clearLocalCache() {}
}
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class SqlExecutionTimeInterceptor implements InnerInterceptor {

    @Override
    public boolean beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        long startTime = System.currentTimeMillis();
        // 可以通过 ThreadLocal 或其他方式存储开始时间
        System.out.println("SQL Preparation Start...");
        // 模拟在 StatementHandler 中设置属性(可选)
        return true; // 返回 true 表示继续执行,false 表示中断
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                            ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        System.out.println("Executing Query: " + boundSql.getSql());
    }

    @Override
    public void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                           ResultHandler resultHandler, BoundSql boundSql, List<Object> resultList) throws SQLException {
        long endTime = System.currentTimeMillis();
        System.out.println("SQL Execution Time: " + (endTime - (System.currentTimeMillis() - /* 假设获取开始时间 */ 1000)) + "ms");
        // 注意:实际实现中需要合理管理开始时间(如使用 ThreadLocal)
    }

    // 其他方法可以留空或根据需求实现
    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {}

    @Override
    public void clearLocalCache() {}
}
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.sql.Connection; import java.sql.SQLException; import java.util.List; public class SqlExecutionTimeInterceptor implements InnerInterceptor { @Override public boolean beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { long startTime = System.currentTimeMillis(); // 可以通过 ThreadLocal 或其他方式存储开始时间 System.out.println("SQL Preparation Start..."); // 模拟在 StatementHandler 中设置属性(可选) return true; // 返回 true 表示继续执行,false 表示中断 } @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { System.out.println("Executing Query: " + boundSql.getSql()); } @Override public void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, List<Object> resultList) throws SQLException { long endTime = System.currentTimeMillis(); System.out.println("SQL Execution Time: " + (endTime - (System.currentTimeMillis() - /* 假设获取开始时间 */ 1000)) + "ms"); // 注意:实际实现中需要合理管理开始时间(如使用 ThreadLocal) } // 其他方法可以留空或根据需求实现 @Override public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {} @Override public void clearLocalCache() {} }

四、InnerInterceptor 的注册

自定义的 InnerInterceptor 需要通过 MyBatis Plus 的配置类注册到插件中。以下是注册示例:

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加自定义 InnerInterceptor
interceptor.addInnerInterceptor(new SqlExecutionTimeInterceptor());
// 也可以添加其他内置拦截器,如分页插件
// interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加自定义 InnerInterceptor
        interceptor.addInnerInterceptor(new SqlExecutionTimeInterceptor());
        // 也可以添加其他内置拦截器,如分页插件
        // interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加自定义 InnerInterceptor interceptor.addInnerInterceptor(new SqlExecutionTimeInterceptor()); // 也可以添加其他内置拦截器,如分页插件 // interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } }

五、常见内置 InnerInterceptor 的使用

1. 分页插件

import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    return interceptor;
}
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }

2. 多租户插件

import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 返回当前线程的租户 ID(示例)
return new StringValue("tenant_id");
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略某些表不添加租户条件
return "common_table".equals(tableName);
}
}));
return interceptor;
}
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
        @Override
        public Expression getTenantId() {
            // 返回当前线程的租户 ID(示例)
            return new StringValue("tenant_id");
        }

        @Override
        public String getTenantIdColumn() {
            return "tenant_id";
        }

        @Override
        public boolean ignoreTable(String tableName) {
            // 忽略某些表不添加租户条件
            return "common_table".equals(tableName);
        }
    }));
    return interceptor;
}
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.StringValue; @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public Expression getTenantId() { // 返回当前线程的租户 ID(示例) return new StringValue("tenant_id"); } @Override public String getTenantIdColumn() { return "tenant_id"; } @Override public boolean ignoreTable(String tableName) { // 忽略某些表不添加租户条件 return "common_table".equals(tableName); } })); return interceptor; }

六、注意事项

  1. 执行顺序:多个 InnerInterceptor 的执行顺序与添加顺序一致。
  2. 性能影响:拦截器会增加 SQL 执行的开销,需合理设计逻辑。
  3. 兼容性:确保自定义拦截器与 MyBatis Plus 版本兼容。

七、总结

InnerInterceptor 是 MyBatis Plus 提供的一个强大扩展点,允许开发者在 SQL 执行的各个阶段插入自定义逻辑。通过实现 InnerInterceptor 接口并注册到 MyBatis Plus 插件中,可以轻松实现分页、多租户、SQL 日志记录等功能。在实际开发中,建议优先使用内置拦截器,必要时再实现自定义逻辑。

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞14 分享
Never say die.
永不言弃
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容