mybatisplus多租户开发,以及定时器中忽略多租户查询,与自动填充数据

话不多说上干货
最近接到一个需求  需要实现一个多租户的需求,实现租户之间的数据隔离,根据mp官网的资料,尝试采用mp官网的多租户插件来实现。
官网地址如下
多租户插件 | MyBatis-Plus
首先  创建一个 MybatisPlusConfig用来做mybatisplus的拦截器,话不多说上代码

@Slf4j
@Configuration
@MapperScan(basePackages = {"com.**.**.mapper"})
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //多租户插件
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                return new LongValue(TenantContextHolder.getTenantId());
            }
            // 这里通过表名过滤当前不需要区分租户查询数据的表
            @Override
            public boolean ignoreTable(String tableName) {
                return Arrays.asList("tenant"
                        ).contains(tableName);
            }
        }){
            @Override
            protected void processInsert(Insert insert, int index, String sql, Object obj) {
                //多租户只对查询进行动态拼接
            }
            @Override
            protected void processUpdate(Update update, int index, String sql, Object obj) {
                //多租户只对查询进行动态拼接
            }
            @Override
            protected void processDelete(Delete delete, int index, String sql, Object obj) {
                //多租户只对查询进行动态拼接
            }
        });
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
        return interceptor;
    }

考虑到租户之间的数据隔离只针对于数据,所以我的想法是只对查询进行对应的租户筛选,所以禁用了修改  新增  删除的租户条件筛选操作,其实很好理解  各个租户只能看到自己的数据  所以也就只能删除对应的数据。

除此之外  我们需要对相关的表增加租户字段,这一点由于我的项目所有的实体都是有继承父类的,所以只需要在父类中添加租户字段即可,然后在对相关表进行添加租户字段,做到这里其实整体的思路已经有了

接下来需要做的就是,如何让租户添加的数据带有当前的租户id,我的实现思路是mp的自动填充字段功能
自动填充字段 | MyBatis-Plus

上代码

@Configuration
@Slf4j
public class MybatisCommonColumnsHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "tenantId", Long.class, TenantContextHolder.getTenantId());
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

需要注意的是自动填充功能只适用于mybatisplus,因此想要使用此功能需要在mapper上extends BaseMapper<class>,否则此功能无法生效。

然后我想大家可能都比较好奇上述代码中TenantContextHolder.getTenantId()这串代码是如何获取到租户id的,其实很简单
 

创建一个静态变量用来存储租户id

public class TenantContextHolder {

    // 静态ThreadLocal变量,用于存储当前线程的租户ID
    static ThreadLocal<Long> tenantThreadLocal = new ThreadLocal<>();

    /**
     * 获取当前线程的租户ID。
     *
     * @return 当前线程的租户ID
     */
    public static Long getCurrentTenantId() {
        return tenantThreadLocal.get();
    }

    /**
     * 设置当前线程的租户ID。
     *
     * @param tenantId 要设置的租户ID
     */
    public static void setCurrentTenantId(Long tenantId) {
        tenantThreadLocal.set(tenantId);
    }

    /**
     * 清除当前线程的租户ID,使得线程不再关联任何租户。
     */
    public static void clear() {
        tenantThreadLocal.remove();
    }

    public static Long getTenantId() {
        if(TenantContextHolder.getCurrentTenantId()!=null)
        {
            Long tenantId = TenantContextHolder.getCurrentTenantId();
            if(tenantId!=null)
            {
                return tenantId;
            }
        }
        return 0L;
    }
}

再去创建一个租户过滤器,拦截每次的请求去获取租户id

@Component
// 过滤器执行顺序注解,值越小优先级越高
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter(urlPatterns = "/**", filterName = "TraceFilter")
public class TenantFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            // 从请求头中获取租户ID
            String tenantId = request.getHeader("tenantId");
			System.out.println("--------------------tenantId = " + tenantId);
			TenantContextHolder.setCurrentTenantId(Long.valueOf(tenantId));
            }
            // 将租户ID转换为Long类型并设置到租户上下文 Threadlocal 中
            filterChain.doFilter(request, response);
        } finally {
            // 清除租户上下文,避免线程池中线程复用导致的数据混乱
            TenantContextHolder.clear();
        }
    }
}

这里提供两种思路,从请求中获取租户id,我们可以和前端商定每次请求中携带租户id,或者把租户id加进token,然后通过解析token获取租户id。

这样就可以实现租户id的获取了。

注意  有时候会遇到这样的场景  比如 某条查询语句不需要用到多租户条件去查询  所以我们需要对这条查询语句做特殊处理,mp官方也给出了相应的解决办法,就是在mapper层对查询语句做@InterceptorIgnore(tenantLine = "true")忽略多租户操作。这样这条查询语句的执行结果就会是不会携带租户id的查询了。
当然还有一种场景  比如在定时器中  上面的注解无法生效,或者  我们采用的查询方法是mp自生集成的方法比如在impl层直接this.list();不需要编写mapper层的方法。这种情况mp官方也提出了相应的方案,可以手动或略租户条件查询操作

// 设置忽略租户插件
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());

// 执行逻辑 …

// 关闭忽略策略
InterceptorIgnoreHelper.clearIgnoreStrategy();

将你需要执行的操作放入手动操作的代码中,那么接下来执行的sql查询不会凭借租户id的查询条件,需要注意的是  完成该操作后需要关闭该操作。
InterceptorIgnoreHelper.clearIgnoreStrategy();
到这里我的分享就结束了,独自开发的路上踩到了许多坑,希望我的分享能对阅读你有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值