Spring Boot集成MyBatis-Plus 自定义拦截器实现动态表名切换功能

 更新时间:2024年11月27日 10:20:25   作者:LOVE_DDZ  
本文介绍了如何在SpringBoot项目中集成MyBatis-Plus,并通过自定义拦截器实现动态表名切换,此外,还探讨了MyBatis拦截器在其他场景中的应用,如SQL日志记录、多租户数据隔离、数据权限控制等,感兴趣的朋友跟随小编一起看看吧

Spring Boot集成MyBatis-Plus:自定义拦截器实现动态表名切换

一、引言

介绍动态表名的场景需求,比如多租户系统、分表分库,或者不同业务模块共用一套代码但操作不同表。说明 MyBatis-Plus 默认绑定固定表名的问题。

二、项目配置

1. 集成 MyBatis-Plus

简单说明如何在 Spring Boot 中引入 MyBatis-Plus 并配置。

2. 依赖添加

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>最新版本号</version>
</dependency>

三、自定义拦截器实现动态表名

1. 拦截器原理

解释 MyBatis 拦截器的核心概念,介绍 Interceptor 接口和 @Signature 注解。

2. 拦截器实现代码

详细展示拦截器的完整实现:

@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DynamicTableInterceptor implements Interceptor {
  	/**
     * 使用ThreadLocal来存储线程特有的数据,这里用于存储动态表名
     */
    private static final ThreadLocal<String> TABLE_NAME_HOLDER = new ThreadLocal<>();
    /**
     * 设置动态表名
     *
     * @param tableName 需要设置的表名,由调用者指定
     *                  此方法允许在运行时动态地设置数据库表名,以便在多数据源或动态表名的场景下灵活地切换表
     */
    public static void setDynamicTableName(String tableName) {
        TABLE_NAME_HOLDER.set(tableName);
    }
    /**
     * 清除当前线程中的动态表名
     * 此方法用于清除ThreadLocal中存储的表名信息,以避免内存泄漏
     * 它应在每次使用动态表名后调用,确保不会对后续的请求产生影响
     */
    public static void clearDynamicTableName() {
        TABLE_NAME_HOLDER.remove();
    }
    /**
     * 拦截器
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        String tableName = TABLE_NAME_HOLDER.get();
        if (tableName != null) {
            /**
             * 从实体类的@tableName中获取表名,必须在实体类上面添加@tableName注解
             * 实体类中的@tableName注解中的值,必须是数据库中的表名,否则会报错
             * 例如 :@TableName("t_user_"),数据库是根据月份来的,在这里替换则需要根据当前月份进行拼接表名
             */
            Class<?> entityType = boundSql.getParameterObject().getClass();
            String oldTableName = entityType.getAnnotation(TableName.class).value();
            String newSql = boundSql.getSql().replace(oldTableName, tableName);
            Field sqlField = boundSql.getClass().getDeclaredField("sql");
            sqlField.setAccessible(true);
            sqlField.set(boundSql, newSql);
        }
        return invocation.proceed();
    }
}

3. 使用拦截器动态设置表名

DynamicTableInterceptor.setDynamicTableName("your_dynamic_table_name");
try {
    myService.saveOrUpdateBatch(entities); // MyBatis-Plus 操作
} finally {
    DynamicTableInterceptor.clearDynamicTableName();
}

四、其他场景

MyBatis 拦截器(Interceptor)是 MyBatis 提供的强大扩展机制,可以拦截执行过程中的不同阶段并进行自定义操作。除了动态修改表名之外,拦截器还可以应用于以下多种场景:

1. SQL 日志记录与审计

  • 场景:记录每次执行的 SQL 语句、参数、执行时间等信息。
  • 用途:用于 SQL 审计、性能监控、问题排查。

示例

@Override
public Object intercept(Invocation invocation) throws Throwable {
    StatementHandler handler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = handler.getBoundSql();
    long startTime = System.currentTimeMillis();
    Object result = invocation.proceed();
    long endTime = System.currentTimeMillis();
    System.out.println("SQL: " + boundSql.getSql() + " Execution Time: " + (endTime - startTime) + "ms");
    return result;
}

2. 多租户数据隔离

  • 场景:根据当前租户信息自动添加过滤条件,确保数据隔离。
  • 用途:实现 SaaS 系统中不同租户的数据访问控制。

示例

@Override
public Object intercept(Invocation invocation) throws Throwable {
    StatementHandler handler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = handler.getBoundSql();
    String originalSql = boundSql.getSql();
    String tenantId = TenantContext.getCurrentTenantId();
    String modifiedSql = originalSql + " WHERE tenant_id = " + tenantId;
    Field sqlField = boundSql.getClass().getDeclaredField("sql");
    sqlField.setAccessible(true);
    sqlField.set(boundSql, modifiedSql);
    return invocation.proceed();
}

3. SQL 参数加密与解密

  • 场景:对敏感数据(如身份证号、电话等)在 SQL 操作时进行自动加密或解密。
  • 用途:提高数据安全性,确保数据存储符合安全规范。

示例

@Override
public Object intercept(Invocation invocation) throws Throwable {
    StatementHandler handler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    // 自定义加密逻辑
    // 加密处理参数
    return invocation.proceed();
}

4. 自动分页处理

  • 场景:在执行查询时自动添加分页逻辑,避免手动分页处理。
  • 用途:简化分页查询的代码。

示例

@Override
public Object intercept(Invocation invocation) throws Throwable {
    StatementHandler handler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = handler.getBoundSql();
    String originalSql = boundSql.getSql();
    String paginatedSql = originalSql + " LIMIT " + offset + ", " + limit;
    Field sqlField = boundSql.getClass().getDeclaredField("sql");
    sqlField.setAccessible(true);
    sqlField.set(boundSql, paginatedSql);
    return invocation.proceed();
}

5. 数据权限控制

  • 场景:根据用户角色或权限,自动添加数据过滤条件。
  • 用途:确保用户只能访问被授权的数据。

示例

@Override
public Object intercept(Invocation invocation) throws Throwable {
    StatementHandler handler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = handler.getBoundSql();
    String originalSql = boundSql.getSql();
    String userRole = SecurityContext.getCurrentUserRole();
    String restrictedSql = originalSql + " AND role = '" + userRole + "'";
    // 动态修改 SQL
    return invocation.proceed();
}

6. 缓存增强

  • 场景:自定义缓存逻辑,拦截查询请求,先检查缓存是否命中。
  • 用途:减少数据库访问次数,提高性能。

示例

@Override
public Object intercept(Invocation invocation) throws Throwable {
    // 检查缓存
    Object cachedResult = CacheManager.get(boundSql.getSql());
    if (cachedResult != null) {
        return cachedResult;
    }
    // 如果没有命中,执行查询
    Object result = invocation.proceed();
    // 存入缓存
    return result;
}

7. 动态数据源切换

场景:根据不同的业务需求动态选择数据源。

用途:实现读写分离或多数据库支持。

示例

@Override
public Object intercept(Invocation invocation) throws Throwable {
    String dataSourceKey = DataSourceContextHolder.getDataSourceKey();
    DynamicDataSource.setDataSourceKey(dataSourceKey);
    return invocation.proceed();
}

总结

MyBatis 拦截器为开发者提供了灵活的扩展能力,可以在 SQL 执行的多个阶段中注入自定义逻辑,从而实现多种高级功能。合理使用拦截器不仅能增强系统功能,还能提升性能和安全性。

到此这篇关于Spring Boot集成MyBatis-Plus 自定义拦截器实现动态表名切换功能的文章就介绍到这了,更多相关Spring Boot MyBatis-Plus 动态表名切换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot异步实现的8种方式

    SpringBoot异步实现的8种方式

    异步执行对于开发者来说并不陌生,在实际的开发过程中,很多场景多会使用到异步,本文主要介绍了SpringBoot异步实现的8种方式,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • Java实现List去重的方法详解

    Java实现List去重的方法详解

    本文用示例介绍Java的List(ArrayList、LinkedList等)的去重的方法。List去重的常用方法一般是:JDK8的stream的distinct、转为HashSet、转为TreeSet等,感兴趣的可以了解一下
    2022-05-05
  • Java实现对华北、华南、华东和华中四个区域的划分

    Java实现对华北、华南、华东和华中四个区域的划分

    在Java中,通过定义枚举类、编写主程序和进行测试,本文详细介绍了如何划分华北、华南、华东和华中四个区域,首先定义枚举类标识区域,然后通过主程序接收用户输入并返回相应区域,最后通过测试用例确保正确性,文章还介绍了甘特图和饼状图的使用
    2024-09-09
  • SpringBoot @PostMapping接收HTTP请求的流数据问题

    SpringBoot @PostMapping接收HTTP请求的流数据问题

    这篇文章主要介绍了SpringBoot @PostMapping接收HTTP请求的流数据问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Java实现冒泡排序简单示例

    Java实现冒泡排序简单示例

    冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来,下面这篇文章主要给大家介绍了关于Java实现冒泡排序的相关资料,需要的朋友可以参考下
    2023-06-06
  • Spring源码解析之循环依赖的实现流程

    Spring源码解析之循环依赖的实现流程

    这篇文章主要介绍了Spring源码解析之循环依赖的实现流程,文章基于Java的相关内容展开循环依赖的实现流程,需要的小伙伴可以参考一下
    2022-07-07
  • Spring整合多数据源实现动态切换的实例讲解

    Spring整合多数据源实现动态切换的实例讲解

    下面小编就为大家带来一篇Spring整合多数据源实现动态切换的实例讲解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • 详解Mybatis 传递参数类型为List的取值问题

    详解Mybatis 传递参数类型为List的取值问题

    这篇文章主要介绍了详解Mybatis 传递参数类型为List的取值问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • mybatis-plus使用generator实现逆向工程

    mybatis-plus使用generator实现逆向工程

    mybatis-plus-generator在3.5.0以及以后的版本使用新的方式逆向生成代码,本文主要介绍了mybatis-plus使用generator实现逆向工程,具有一定的参考价值,感兴趣的可以了解一下
    2022-05-05
  • jstl EL表达式遍历Map的方法

    jstl EL表达式遍历Map的方法

    这篇文章主要介绍了jstl EL表达式遍历Map的方法的相关资料,需要的朋友可以参考下
    2016-12-12

最新评论