mybatis-plus基于行级别的多租户实现

Laughing
2021-04-22 / 0 评论 / 1,610 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2024年03月18日,已超过305天没有更新,若内容或图片失效,请留言反馈。

什么是多租户

多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性。简单讲:在一台服务器上运行单个应用实例,它为多个租户(客户)提供服务。从定义中我们可以理解:多租户是一种架构,目的是为了让多用户环境下使用同一套程序,且保证用户间数据隔离。那么重点就很浅显易懂了,多租户的重点就是同一套程序下实现多用户数据的隔离。

多租户架构以及数据隔离方案

方案一:独立数据库

即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本较高。

优点:为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;如果出现故障,恢复数据比较简单。

缺点:增多了数据库的安装数量,随之带来维护成本和购置成本的增加。

方案二:共享数据库,独立 Schema

也就是说 共同使用一个数据库 使用表进行数据隔离
多个或所有租户共享Database,但是每个租户一个Schema(也可叫做一个user)。底层库比如是:DB2、ORACLE等,一个数据库下可以有多个SCHEMA。

优点:为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可支持更多的租户数量。

缺点:如果出现故障,数据恢复比较困难,因为恢复数据库将牵涉到其他租户的数据;

方案三:共享数据库,共享 Schema,共享数据表

也就是说 共同使用一个数据库一个表 使用字段进行数据隔离

即租户共享同一个Database、同一个Schema,但在表中增加TenantID多租户的数据字段。这是共享程度最高、隔离级别最低的模式。

简单来讲,即每插入一条数据时都需要有一个客户的标识。这样才能在同一张表中区分出不同客户的数据,这也是我们系统目前用到的(tenant_id)

优点:三种方案比较,第三种方案的维护和购置成本最低,允许每个数据库支持的租户数量最多。

缺点:隔离级别最低,安全性最低,需要在设计开发时加大对安全的开发量;数据备份和恢复最困难,需要逐表逐条备份和还原。

基于行级别的隔离

本文基于方案三:共享数据库,共享 Schema,共享数据表进行说明。

关于MyBatis-Plus的基础配置,可以参考MyBatis-Plus简易使用教程

使用

表结构

create table user
(
    id        bigint auto_increment comment '主键ID'
        primary key,
    name      varchar(30)      null comment '姓名',
    age       int              null comment '年龄',
    email     varchar(50)      null comment '邮箱',
    del_flag  char default 'N' null,
    tenant_id bigint           null comment '租户'
);

预置数据

INSERT INTO user (id, name, age, email, del_flag, tenant_id) VALUES (1, 'Jone', 18, 'test1@baomidou.com', 'Y', 1);
INSERT INTO user (id, name, age, email, del_flag, tenant_id) VALUES (2, 'Jack', 20, 'test2@baomidou.com', 'N', 2);
INSERT INTO user (id, name, age, email, del_flag, tenant_id) VALUES (3, 'Tom', 28, 'test3@baomidou.com', 'N', 1);
INSERT INTO user (id, name, age, email, del_flag, tenant_id) VALUES (4, 'Sandy', 21, 'test4@baomidou.com', 'N', 2);
INSERT INTO user (id, name, age, email, del_flag, tenant_id) VALUES (5, 'Billie', 24, 'test5@baomidou.com', 'N', 1);

mybatis-plus配置

在mybatis-plus配置类中,定义拦截器,并加入租户相关拦截器TenantLineInnerInterceptor

@Configuration
public class MybatisPlusConfig {


    // 最新版
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        TenantLineHandler tenantLineHandler = new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                return new LongValue(1);
            }

            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            @Override
            public boolean ignoreTable(String tableName) {
                return false;
            }
        };
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }

}

测试

1.基于mybatis-plus的CRUD操作

@GetMapping("list")
    public List<User> list(){
        return userService.list();
    }

2.自定义sql测试

UserMapper.java

/**
 * 租户的查询
 * @return
 */
List<User> selectUserTenant();

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="net.xiangcaowuyu.mybatisplussample.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="net.xiangcaowuyu.mybatisplussample.domain.User">
        <!--@Table `user`-->
        <id column="id" jdbcType="BIGINT" property="id"/>
        <result column="name" jdbcType="VARCHAR" property="name"/>
        <result column="age" jdbcType="INTEGER" property="age"/>
        <result column="email" jdbcType="VARCHAR" property="email"/>
    </resultMap>
    <sql id="Base_Column_List">
        id, `name`, age, email
    </sql>

    <select id="selectUserList" resultMap="BaseResultMap">
        select <include refid="Base_Column_List"/> from user
    </select>

    <select id="selectUserTenant" resultMap="BaseResultMap">
        select <include refid="Base_Column_List"/> from user
    </select>
</mapper>
@GetMapping(value = "listTenant")
public List<User> listTenant() {
    return userService.selectUserTenant();
}

3.增加数据

@GetMapping("add")
public Boolean addUser() {
    User user = new User();
    user.setName("张三");
    user.setAge(30);
    return userService.save(user);
}
0

评论 (0)

取消
  1. 头像
    Panacea
    Windows 7 · Google Chrome

    希望好用,最近在用谷歌的,但是会出现把H翻译成11的情况,很恼人

    回复