什么是多租户
多租户技术或称多重租赁技术,简称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);
}
希望好用,最近在用谷歌的,但是会出现把H翻译成11的情况,很恼人