Mybatis-Plus 3.4.0多租户的实现方案

Posted MateCloud微服务

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis-Plus 3.4.0多租户的实现方案相关的知识,希望对你有一定的参考价值。

使用背景

微服务matecloud希望支持saas的多租户的管理模式,mybatis plus正好已经考虑支持该模式,下面就简单说说其应用案例

多租户模式介绍

数据隔离有三种方案:

1、独立数据库:简单来说就是一个租户使用一个数据库,这种数据隔离级别最高,安全性最好,但是提高成本。

2、共享数据库、隔离数据架构:多租户使用同一个数据裤,但是每个租户对应一个Schema(数据库user)。-

3、共享数据库、共享数据架构:使用同一个数据库,同一个Schema,但是在表中增加了租户ID的字段,这种共享数据程度最高,隔离级别最低。下面样例以共郭数据库、共享数据结构的形式的讲述

集成步骤

1. 加依赖

<!--Mybatis-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.4.0</version>
        </dependency>

主要是这两个依赖

2. 写代码

2.1. 租户属性

package vip.mate.core.database.props;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;

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

/**
 * 租户属性
 * @author xuzhanfu
 * @Date 2020-9-6
 */
@Getter
@Setter
@RefreshScope
@ConfigurationProperties(prefix = "mate.tenant")
public class TenantProperties 

    /**
     * 是否开启租户模式
     */
    private Boolean enable = true;

    /**
     * 需要排除的多租户的表
     */
    private List<String> ignoreTables = Arrays.asList("mate_sys_user", "mate_sys_depart", "mate_sys_role", "mate_sys_tenant", "mate_sys_role_permission");

    /**
     * 多租户字段名称
     */
    private String column = "tenant_id";

    /**
     * 排除不进行租户隔离的sql
     * 样例全路径:vip.mate.system.mapper.UserMapper.findList
     */
    private List<String> ignoreSqls = new ArrayList<>();


2.2. 租户配置

package vip.mate.core.database.config;

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import vip.mate.core.common.context.TenantContextHolder;
import vip.mate.core.database.props.TenantProperties;

/**
 * 多租户配置中心
 * @author pangu
 * @Date 2020-9-7
 */
@Configuration
@AllArgsConstructor
@AutoConfigureBefore(MybatisPlusConfiguration.class)
@EnableConfigurationProperties(TenantProperties.class)
public class TenantConfiguration 

    private final TenantProperties tenantProperties;

    /**
     * 新多租户插件配置,一缓和二缓遵循mybatis的规则,
     * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false
     * 避免缓存万一出现问题
     * @return
     */
    @Bean
    public TenantLineInnerInterceptor tenantLineInnerInterceptor()
        return new TenantLineInnerInterceptor(new TenantLineHandler() 
            /**
             * 获取租户ID
             * @return
             */
            @Override
            public Expression getTenantId() 
                String tenant = TenantContextHolder.getTenantId();
                if (tenant != null) 
                    return new StringValue(TenantContextHolder.getTenantId());
                
                return new NullValue();
            

            /**
             * 获取多租户的字段名
             * @return String
             */
            @Override
            public String getTenantIdColumn() 
                return tenantProperties.getColumn();
            

            /**
             * 过滤不需要根据租户隔离的表
             * 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
             * @param tableName 表名
             */
            @Override
            public boolean ignoreTable(String tableName) 
                return tenantProperties.getIgnoreTables().stream().anyMatch(
                        (t) -> t.equalsIgnoreCase(tableName)
                );
            
        );
    


2.3. Mybatis plus配置

package vip.mate.core.database.config;

import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import vip.mate.core.common.factory.YamlPropertySourceFactory;
import vip.mate.core.database.handler.MateMetaObjectHandler;
import vip.mate.core.database.props.TenantProperties;

/**
 * mybatis plus配置中心
 * @author xuzhanfu
 */
@Slf4j
@Configuration
@AllArgsConstructor
@EnableTransactionManagement
@PropertySource(factory = YamlPropertySourceFactory.class, value = "classpath:mate-db.yml")
@MapperScan("vip.mate.**.mapper.**")
public class MybatisPlusConfiguration 

    private final TenantProperties tenantProperties;

    private final TenantLineInnerInterceptor tenantLineInnerInterceptor;

    /**
     * 单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法)
     */
    private static final Long MAX_LIMIT = 1000L;

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,
     * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false
     * 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor paginationInterceptor() 
        boolean enableTenant = tenantProperties.getEnable();
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        if (enableTenant) 
            interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
        
        //分页插件: PaginationInnerInterceptor
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setMaxLimit(MAX_LIMIT);
        //防止全表更新与删除插件: BlockAttackInnerInterceptor
        BlockAttackInnerInterceptor blockAttackInnerInterceptor = new BlockAttackInnerInterceptor();
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor);
        return interceptor;
    

    @Bean
    public ConfigurationCustomizer configurationCustomizer() 
        return configuration -> configuration.setUseDeprecatedExecutor(Boolean.FALSE);
    

    /**
     * 自动填充数据
     */
    @Bean
    @ConditionalOnMissingBean(MateMetaObjectHandler.class)
    public MateMetaObjectHandler mateMetaObjectHandler()
        MateMetaObjectHandler metaObjectHandler = new MateMetaObjectHandler();
        log.info("MateMetaObjectHandler []", metaObjectHandler);
        return metaObjectHandler;
    


注意看下注释

剩下的就是前端将tenant_id这个字段全局设定,并传过来即可

代码样例

https://github.com/matevip/matecloud

以上是关于Mybatis-Plus 3.4.0多租户的实现方案的主要内容,如果未能解决你的问题,请参考以下文章

如何解决使用mybatis-plus提供的多租户插件出现Column ‘tenant_id‘ specified twice问题

JeecgBoot开发多租户SAAS数据隔离,查询数据库,改造多租户后 Mybatis-plus 查询数据库的SQL语句tenant-id[租户Id] 一直为0

JeecgBoot开发多租户SAAS数据隔离,查询数据库,改造多租户后 Mybatis-plus 查询数据库的SQL语句tenant-id[租户Id] 一直为0

阿里二面之lazada跟国际化中台区别,以及多租户的学习

Idea+maven+spring-cloud项目搭建系列--13 整合MyBatis-Plus多数据源dynamic-datasource

mybatis-plus默认值问题