使用Mybatis-Plus让你的工程升级支持租户模式

449 阅读2分钟

前言

以前公司的项目是每个城市使用不同的数据库,从物理上就解决了不同城市用户(租户)带来的数据隔离的问题。现在要求各个城市的数据部署在一个数据库中,在同一张表中,因此需要对现有的工程进行多租户模式改造。而基于修改最少代码的原则下,想到了使用Mybatis-Plus的增强功能是否能够实现对sql语句的自定义修改,可喜的是当我进入Mybatis-Plus的官网,正好看到了有对多租户模式的支持。下面来介绍如何将你的项目升级支持多租户模式。

 1. 引入Mybatis-Plus的依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>

2. 编写核心Handler

package xxx;

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


/**
 * @author xxx
 * 租户维护处理器
 */
@Slf4j
@Component
public class TenantHandler implements TenantLineHandler {
   @Autowired
   private TenantConfigProperties properties;

   /**
    * 获取租户值
    * <p>
    *
    * @return 租户值
    */
   @Override
   public Expression getTenantId() {
      String tenantId = TenantContextHolder.getTenantId();
      log.debug("当前租户为 >> {}", tenantId);

      if (tenantId == null) {
         return new NullValue();
      }
      return new StringValue(tenantId);
   }

   /**
    * 获取租户字段名
    *
    * @return 租户字段名
    */
   @Override
   public String getTenantIdColumn() {
      return properties.getColumn();
   }

   /**
    * 根据表名判断是否进行过滤
    *
    * @param tableName 表名
    * @return 是否进行过滤
    */
   @Override
   public boolean ignoreTable(String tableName) {
      String tenantId = TenantContextHolder.getTenantId();
      // 租户中ID 为空,查询全部,不进行过滤
      if (tenantId == null) {
         return Boolean.TRUE;
      }

      return !properties.getTables().contains(tableName);
   }
}

TenantHandler需要实现TenantLineHandler接口,重写其中的三个方法:

  1. getTenantId():MyBatis-Plus 获取租户值的方法,需要返回正确的租户Id,前端的每次请求我都放到了TenantContextHolder的threadLocal中,每次从threadLocal中获取。
  2. getTenantIdColumn(): 这里需要获取到数据库表中的租户字段, MyBatis-Plus框架会根据此方法获取该字段名称。
  3. ignoreTable():根据表名判断是否对该表进行多租户的处理,返回false代表该表需要进行多租户处理,true表示忽略。

3. 编写TenantContextHolder

package com.aipark.aiot.ao.common.datesource.tenant;

import lombok.experimental.UtilityClass;

@UtilityClass
public class TenantContextHolder {

   private final ThreadLocal<String> THREAD_LOCAL_TENANT = new ThreadLocal<>();


   /**
    * TTL 设置租户ID
    *
    * @param tenantId
    */
   public void setTenantId(String tenantId) {
      THREAD_LOCAL_TENANT.set(tenantId);
   }

   /**
    * 获取TTL中的租户ID
    *
    * @return
    */
   public String getTenantId() {
      return THREAD_LOCAL_TENANT.get();
   }

   public void clear() {
      THREAD_LOCAL_TENANT.remove();
   }
}

4. 编写租户配置类

package xxx;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 多租户配置
 *
 * @author xxx
 */
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "config.tenant")
public class TenantConfigProperties {

   /**
    * 维护租户列名称
    */
   private String column;

   /**
    * 多租户的数据表集合
    */
   private List<String> tables = new ArrayList<>();
}

5. 添加MybatisPlusInterceptor拦截器

package xxx;

import com.aipark.aiot.ao.common.datesource.config.MybatisPlusMetaObjectHandler;
import com.aipark.aiot.ao.common.datesource.resolver.SqlFilterArgumentResolver;
import com.aipark.aiot.ao.common.datesource.tenant.TenantLineInterceptor;
import com.aipark.aiot.ao.common.datesource.tenant.TenantHandler;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * @author 
 * @className MybatisAutoConfiguration
 * @description MybatisAutoConfiguration
 */
@Configuration(proxyBeanMethods = false)
public class MybatisAutoConfiguration implements WebMvcConfigurer {

   @Autowired
   private TenantHandler tenantHandler;


   /**
    * 分页和租户插件, 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型
    */
   @Bean
   public MybatisPlusInterceptor mybatisPlusInterceptor() {
      MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
      // 添加多租户配置
      interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler));
      interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
      return interceptor;
   }

}

6. yml配置

config:
  tenant:
    column: tenant_id
    tables:
      - sys_role
      - sys_config

到此,你的项目便可以支持多租户模式啦~