当前位置: 首页 > news >正文

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');

Maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mybatis-plus-samples</artifactId>
        <groupId>com.baomidou</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mybatis-plus-sample-tenant</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!-- MySQL 连接驱动依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/mapper/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

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

代码

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<String> 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);
//    }
}

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";
	
	/**
	 * 忽略多租户的表名
	 * <pre>
	 * 数据库中物理表表名
	 * </pre>
	 */
	private List<String> ignoreTables = new ArrayList<>();
	
}

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;

/**
 * <p>
 * 用户实体对应表 user
 * </p>
 *
 */
@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;

}

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;

/**
 * <p>
 * MP 支持不需要 UserMapper.xml 这个模块演示内置 CRUD 咱们就不要 XML 部分了
 * </p>
 *
 */
public interface UserMapper extends BaseMapper<User> {

    /**
     * 自定义SQL:默认也会增加多租户条件
     * 参考打印的SQL
     * @return
     */
    Integer myCount();

    List<User> getUserAndAddr(@Param("username") String username);

    List<User> getAddrAndUser(@Param("name") String name);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.baomidou.mybatisplus.samples.tenant.mapper.UserMapper">

    <select id="myCount" resultType="java.lang.Integer">
        select count(1) from user
    </select>

    <select id="getUserAndAddr" resultType="com.baomidou.mybatisplus.samples.tenant.entity.User">
        select u.id, u.name, a.name as addr_name
        from user u
        left join user_addr a on a.user_id=u.id
        <where>
            <if test="username!=null">
                u.name like concat(concat('%',#{username}),'%')
            </if>
        </where>
    </select>

    <select id="getAddrAndUser" resultType="com.baomidou.mybatisplus.samples.tenant.entity.User">
        select a.name as addr_name, u.id, u.name
        from user_addr a
        left join user u on u.id=a.user_id
        <where>
            <if test="name!=null">
                a.name like concat(concat('%',#{name}),'%')
            </if>
        </where>
    </select>
</mapper>

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;

/**
 * <p>
 * 多租户 Tenant 演示
 * </p>
 *
 * @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<User> 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);
    }
}

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

相关文章:

  • 使用Python以UCI心脏病数据集为例,进行数据简单分析
  • URL编码解码详解
  • win10+ubuntu双系统下载ubuntu方法(卸载系统不完整会进入grub)
  • Spring事务传播机制
  • 实现深度理解函数指针
  • C/C++常用预编译指令介绍
  • 杰理强制升级工具4.0使用和原理解析
  • Vue3介绍和安装
  • Linux命令--权限(chmod、chown)--使用/实例
  • flink-sql所有语法详解
  • 【图像分割】基于matlab萤火虫算法图像聚类分割【含Matlab源码 2106期】
  • SQL 入门之第一讲——MySQL 8.0.29安装教程(windows 64位)
  • 用Python进行数学建模(一)
  • 力扣:669. 修剪二叉搜索树,今日份快乐
  • java毕业设计KTV点歌系统mybatis+源码+调试部署+系统+数据库+lw
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • ES2017异步函数现已正式可用
  • java2019面试题北京
  • JavaScript 基本功--面试宝典
  • jQuery(一)
  • Laravel 菜鸟晋级之路
  • linux学习笔记
  • mysql中InnoDB引擎中页的概念
  • Netty 4.1 源代码学习:线程模型
  • react-native 安卓真机环境搭建
  • SOFAMosn配置模型
  • spring cloud gateway 源码解析(4)跨域问题处理
  • vue-loader 源码解析系列之 selector
  • Vue全家桶实现一个Web App
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 官方解决所有 npm 全局安装权限问题
  • 诡异!React stopPropagation失灵
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 模型微调
  • 你不可错过的前端面试题(一)
  • 扑朔迷离的属性和特性【彻底弄清】
  • 思考 CSS 架构
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​DB-Engines 12月数据库排名: PostgreSQL有望获得「2020年度数据库」荣誉?
  • !!Dom4j 学习笔记
  • #预处理和函数的对比以及条件编译
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (翻译)terry crowley: 写给程序员
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (一)Java算法:二分查找
  • (一一四)第九章编程练习
  • (转) ns2/nam与nam实现相关的文件
  • (转)甲方乙方——赵民谈找工作
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • **PHP二维数组遍历时同时赋值
  • 、写入Shellcode到注册表上线