mybatis-plus的多租户功能实现

2022-04-12 14:05:02

1.创建一个数据表(包含租户ID字段–tenant_id)

CREATE TABLE`t_sys_user`(`user_id` bigint(50) NOT NULL AUTO_INCREMENT COMMENT'用户ID',`user_name` varchar(30) NOT NULL COMMENT'用户名',`user_password` varchar(128) NOT NULL COMMENT'用户密码',`salt` varchar(64) DEFAULT NULL COMMENT'加密盐',`user_phone` varchar(20) DEFAULT NULL COMMENT'手机号',`user_email` varchar(20) DEFAULT NULL COMMENT'邮箱',`user_title` varchar(20) DEFAULT NULL COMMENT'职称',`creater_id` bigint(20) DEFAULT NULL COMMENT'创建人ID',`creater_name` varchar(30) DEFAULT NULL COMMENT'创建人名称',`creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT'创建时间',`updater_id` bigint(20) DEFAULT NULL COMMENT'更新人ID',`updater_name` varchar(30) DEFAULT NULL COMMENT'更新人名称',`updater_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT'更新时间',`role_ids` varchar(200) DEFAULT NULL,`role_names` varchar(300) DEFAULT NULL,`del_tag` int(1) DEFAULT'0' COMMENT'逻辑删除(0 未删除,1 已删除)',`version` bigint(20) DEFAULT'1',`tenant_id` bigint(20) NOT NULL COMMENT'服务商ID',
  PRIMARY KEY(`user_id`))ENGINE=InnoDBAUTO_INCREMENT=37 DEFAULTCHARSET=utf8;

2.编写一个缓存租户id的类:

@Component
public class ApiContext{
    private static final String KEY_CURRENT_TENANT_ID="KEY_CURRENT_TENANT_ID";
    private static final Map<String, Object> mContext= new ConcurrentHashMap<>();

    public void setCurrentTenantId(Long providerId){
        mContext.put(KEY_CURRENT_TENANT_ID, providerId);}
    public LonggetCurrentTenantId(){return(Long) mContext.get(KEY_CURRENT_TENANT_ID);}}

3.在分页的时候,自动在sql拼接租户id的字段作为where后面的条件,具体在mybatis-plus的配置类里面配置,如下:

import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;import com.lz.hehuorenservice.common.component.ApiContext;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.expression.LongValue;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig{
    @Autowired
    private ApiContext apiContext;
    // 最新版  分页插件
    @Bean
    public MybatisPlusInterceptormybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor= new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //注册乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

        /**
         * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
         */
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(newTenantLineHandler(){
            @Override
            public ExpressiongetTenantId(){
                // 从当前系统上下文中取出当前请求的服务商ID,通过解析器注入到SQL中。
                Long currentProviderId= apiContext.getCurrentTenantId();if(null== currentProviderId){
                    throw new RuntimeException("Get CurrentProviderId error.");}return new LongValue(currentProviderId);}

            // 这是 default 方法,默认返回false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName){returnfalse;
               //return"t_sys_user".equalsIgnoreCase(tableName);}}));return interceptor;}}

4.在登录的时候获取到用户的租户ID,并缓存到第二步的类里面,提供给第三步的mybatis-plus的配置类使用,本人是整合在spring security里面,所以在用户的实现类的loadUserByUsername方法里面,获取到用户信息后,把租户id缓存到第二步的类里面。如下配置:

@Service
public class UserServiceImpl extends BaseServiceImpl<User, Long>
    implements UserService, UserDetailsService{

  @Autowired UserDao userDao;
  @Autowired
  private ApiContext apiContext;

  @Override
  public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{
    User user= userDao.getUserByName(userName);
    apiContext.setCurrentTenantId(user.getTenantId());if(user== null){
      throw new UsernameNotFoundException("账户不存在");}
    Set<String> permissions= userDao.getPermissionByUserId(user.getUserId());
    user.setPermissions(permissions);return user;}}

5.需要单独排除登录的接口不需要拼接租户id。因为登录之前是无法获取到租户id的,只有在登录成功后,才能获取到租户id,并进行租户id的缓存。所以要在mapper的方法里面的登录方法上,加一个过滤方法的注释@InterceptorIgnore(tenantLine = “1”)。如下:

@Mapper
public interface UserDao extends BaseDao<User, Long>{

  @InterceptorIgnore(tenantLine="1")
  User getUserByName(String userName);}

完成以上5个步骤,就可以实现多租户的功能。

6.mybatis-plus版本说明:

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

7.期间遇到个问题:

1.使用mybatis-plus的自带的分页的方法查询分页,count(*)的sql统计语句无法拼接上租户id。只有自己实现的分页方法才自动拼接上租户id。(如果路过的高手知道怎么实现,请多多指点)。

  • 作者:心宽路阔走天下
  • 原文链接:https://blog.csdn.net/weixin_42471170/article/details/121137934
    更新时间:2022-04-12 14:05:02