• mybatis-plus实现多租户Saas


    mybatis-plus实现多租户Saas

    实现方式

    给对应需要进行数据隔离的表加上租户ID (tenant_id字段)

    原理

    使用mybatis-plus操作的表会自动过滤tenant_id字段的值
    包括新增,修改,删除,查询,关联查询等

    步骤

    1.给对应表加上tenant_id字段

    2.配置mybatis-plus

    项目结构

    在这里插入图片描述

    初始数据

    DROP TABLE IF EXISTS user;
    
    CREATE TABLE user
    (
    	id BIGINT(20) NOT NULL COMMENT '主键ID',
    	tenant_id BIGINT(20) NOT NULL COMMENT '租户ID',
    	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    	PRIMARY KEY (id)
    );
    
    DROP TABLE IF EXISTS user_addr;
    
    CREATE TABLE USER_ADDR
    (
      id BIGINT(20) NOT NULL COMMENT '主键ID',
      user_id BIGINT(20) NOT NULL COMMENT 'user.id',
      name VARCHAR(30) NULL DEFAULT NULL COMMENT '地址名称',
      PRIMARY KEY (id)
    );
    
    DELETE FROM user;
    
    INSERT INTO user (id, tenant_id, name) VALUES
    (1, 1, 'Jone'),(2, 1, 'Jack'),(3, 1, 'Tom'),
    (4, 0, 'Sandy'),(5, 0, 'Billie');
    
    INSERT INTO user_addr (id, USER_ID, name) VALUES
    (1, 1, 'addr1'),(2,1,'addr2');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    Maven依赖

    
    
        
            mybatis-plus-samples
            com.baomidou
            0.0.1-SNAPSHOT
        
        4.0.0
    
        mybatis-plus-sample-tenant
    
        
            
                org.springframework.boot
                spring-boot-starter
            
            
                com.baomidou
                mybatis-plus-boot-starter
            
            
            
                mysql
                mysql-connector-java
            
        
    
        
            
                
                    src/main/java
                    false
                    
                        **/mapper/*.xml
                    
                
                
                    src/main/resources
                    
                        **/*
                    
                
            
            
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                
            
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    yml配置

    # DataSource Config
    spring:
      datasource:
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/rest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true
    
    
    
    # Logger Config
    logging:
      level:
        com.baomidou.mybatisplus.samples: debug
    # MyBatis-Plus 配置
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志打印
    system:
      saas:
        ignoreTables: # 需要忽略的表,表中无租户ID字段
          - user_addr
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    代码

    MybatisPlusConfig.java

    package com.baomidou.mybatisplus.samples.tenant.config;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
    import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
    
    import net.sf.jsqlparser.expression.Expression;
    import net.sf.jsqlparser.expression.LongValue;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    
    @Configuration
    @MapperScan("com.baomidou.mybatisplus.samples.tenant.mapper")
    public class MybatisPlusConfig {
    
        @Autowired
        private SaaSConfig saaSConfig;
        /**
         * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
         */
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            // 添加租户
            interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
                // todo 获取当前用户租户ID
                Long tenantId = 1L;
    
                /**
                 * 设置租户ID的值
                 * @return
                 */
                @Override
                public Expression getTenantId() {
                    return new LongValue(tenantId);
                }
    
                // 这是 default 方法,默认返回 tenant_id 表示租户ID字段名称,租户字段为tenant_id时,不用重写此方法
                @Override
                public String getTenantIdColumn() {
                    return "tenant_id";
                }
    
                // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
                @Override
                public boolean ignoreTable(String tableName) {
                    // 超级管理员端的表可以进行忽略(如 租户管理表等)
                    List ignoreTables = saaSConfig.getIgnoreTables();
                    long count = ignoreTables.stream().filter(e -> e.equalsIgnoreCase(tableName)).count();
                    return count > 0;
                    //return !"user".equalsIgnoreCase(tableName);
                }
            }));
            // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
            // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
    //        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
            return interceptor;
        }
    
    //    @Bean
    //    public ConfigurationCustomizer configurationCustomizer() {
    //        return configuration -> configuration.setUseDeprecatedExecutor(false);
    //    }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    SaaSConfig.java

    package com.baomidou.mybatisplus.samples.tenant.config;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 多租户的配置属性
     *
     * @company
     *
     */
    @Data
    @Component
    @ConfigurationProperties(prefix = "system.saas")
    public class SaaSConfig {
    
    	/**
    	 * 多租户字段名
    	 */
    	private String tenantId = "tenant_id";
    	
    	/**
    	 * 忽略多租户的表名
    	 * 
    	 * 数据库中物理表表名
    	 * 
    */ private List ignoreTables = new ArrayList<>(); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    User.java

    package com.baomidou.mybatisplus.samples.tenant.entity;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    
    import com.baomidou.mybatisplus.annotation.TableId;
    import lombok.Data;
    import lombok.experimental.Accessors;
    
    /**
     * 

    * 用户实体对应表 user *

    * */ @Data @Accessors(chain = true) public class User { @TableId(type = IdType.AUTO) private Long id; /** * 租户 ID */ private Long tenantId; private String name; @TableField(exist = false) private String addrName; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    UserMapper.java

    package com.baomidou.mybatisplus.samples.tenant.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.baomidou.mybatisplus.samples.tenant.entity.User;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    /**
     * 

    * MP 支持不需要 UserMapper.xml 这个模块演示内置 CRUD 咱们就不要 XML 部分了 *

    * */ public interface UserMapper extends BaseMapper { /** * 自定义SQL:默认也会增加多租户条件 * 参考打印的SQL * @return */ Integer myCount(); List getUserAndAddr(@Param("username") String username); List getAddrAndUser(@Param("name") String name); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    UserMapper.xml

    
    
    
    
        
    
        
    
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    TenantTest.java

    package com.baomidou.mybatisplus.samples.tenant;
    
    import com.baomidou.mybatisplus.samples.tenant.entity.User;
    import com.baomidou.mybatisplus.samples.tenant.mapper.UserMapper;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * 

    * 多租户 Tenant 演示 *

    * * @author hubin * @since 2018-08-11 */ @SpringBootTest public class TenantTest { @Resource private UserMapper mapper; @Test public void aInsert() { User user = new User(); user.setName("一一33"); Assertions.assertTrue(mapper.insert(user) > 0); user = mapper.selectById(user.getId()); Assertions.assertTrue(1 == user.getTenantId()); } @Test public void bDelete() { Assertions.assertTrue(mapper.deleteById(3L) > 0); } @Test public void cUpdate() { Assertions.assertTrue(mapper.updateById(new User().setId(1L).setName("mp")) > 0); } @Test public void dSelect() { List userList = mapper.selectList(null); userList.forEach(u -> Assertions.assertTrue(1 == u.getTenantId())); } /** * 自定义SQL:默认也会增加多租户条件 * 参考打印的SQL */ @Test public void manualSqlTenantFilterTest() { System.out.println(mapper.myCount()); } @Test public void testTenantFilter(){ mapper.getAddrAndUser(null).forEach(System.out::println); mapper.getAddrAndUser("add").forEach(System.out::println); mapper.getUserAndAddr(null).forEach(System.out::println); mapper.getUserAndAddr("J").forEach(System.out::println); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    参考:https://baomidou.com/pages/aef2f2/#tenantlineinnerinterceptor

  • 相关阅读:
    【Lilishop商城】No2-6.确定软件架构搭建五(本篇包括定时任务xxl-job)
    hutool 工具类提高编码效率
    数据存储(非数据库版)
    (179)Verilog HDL:设计一个数字表之Exams/ece241 2014 q7b
    智能催收系统中自然语言理解模块设计
    JAVA反射
    Linux常用操作汇总:内容有点杂,但很实用
    SpringBoot SpringBoot 原理篇 2 自定义starter 2.7 开启yml 提示功能
    查询服务器上所有SQL SERVER数据库中是否包含某个字段,且该字段是否包含某个值
    Doris启停脚本
  • 原文地址:https://blog.csdn.net/weixin_44684303/article/details/126784855