Sass多租户的4种实现方案,简单易懂,附上源码

Sass多租户的4种实现方案,简单易懂,附上源码

你好呀,我是小羊。

你一定听说过Sass 平台这个概念,比如你在淘宝上开了一家店,对于淘宝来说,新开的店铺可以发布商品,售卖商品,发放优惠券等等功能。每个人都能开自己独立的店铺互不影响,这就是多租户的功能。那么这种架构是怎么设计的呢,今天我们就来聊一聊多租户。

Sass多租户的4种实现方案,简单易懂,附上源码

1.什么是Sass ?

多租户技术或称多重租赁技术,简称SaaS,是一种软件架构模式,其中单个实例的软件服务可以同时为多个用户(通常是不同的组织或实体)提供服务。每个用户被称为一个“租户”,他们可以独立地访问和使用软件服务,而且彼此之间相互隔离,就好像每个租户都在使用自己的独立实例一样。

2.多租户的实现方案有哪几种?

既然知道了什么是多租户,就可以知道多租户的核心就是把不同的租户进行数据隔离。有哪几种方式呢?一般有四种。

1.  分库

每个租户有自己独立的数据库,应用程序根据登录的租户去不同的数据库处理数据,数据隔离级别最高,即便其中一个数据库挂掉了,也不会影响到其他租户,数据不会互相影响。但是开发维护成本很高。


Sass多租户的4种实现方案,简单易懂,附上源码

2.  Schema

共享数据库、隔离数据架构:多租户使用同一个数据裤,但是每个租户对应一个Schema(数据库user)。优点是可以共享硬件资源,但是也有不少的改造量,每一个租户都必须新增一个schema。而且主流的mysql 不支持这种模式。

Sass多租户的4种实现方案,简单易懂,附上源码

3.  分表或者分区

在同一个数据库创建不同的表用于区分,每新建一个租户,就新建对应的表或者分区,应用程序根据租户去不同的表或者分区处理数据,相对第一种方案,成本低一点,但是维护起来也很麻烦。

Sass多租户的4种实现方案,简单易懂,附上源码

4.  字段区分

在同样的业务表增加租户id用于区分不同租户,应用程序只需要传入对应的租户id当成条件来处理数据,数据隔离级别较低。优点是成本较低,比较好维护。数据迁移等工作也比较好处理。

Sass多租户的4种实现方案,简单易懂,附上源码

3.方案选择

综上几种实现方式,对应金融等数据隔离要求非常高的系统来说,使用分库分表会比较合适,普通的业务系统,使用表字段来实现多租户会比较合适。表字段来实现多租户有几大优势:

  1. 成本最低
  2. 扩展性最好
  3. 适合快速扩展
  4. 易于维护
  5. 逻辑简单

而表字段来区分的缺陷也可以通过其他方式来克服,比如数据量大的情况可以通过增加硬件配置或者分库分表。数据隔离可以让应用程序来保证。

表字段的方式来做多租户实现非常简单,我们可以建几张表来做一个简单的多租户管理。

用户表,使用 tenant_id 来区分不同的租户

CREATE TABLE sys_user
(
 id BIGINT NOT NULL COMMENT '主键ID',
 tenant_id BIGINT NOT NULL COMMENT '租户ID',
 name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
 PRIMARY KEY (id)
);

插入一些数据

INSERT INTO sys_user (id, tenant_id, name) VALUES
(1, 1, 'Jone'),(2, 1, 'Jack'),(3, 1, 'Tom'),
(4, 0, 'Sandy'),(5, 0, 'Billie');

如果我们想查不同租户的数据,就可以把 tenant_id 当成一个条件来筛选。

select * from sys_user where tenant_id = 1

这样很简单,但是也面临一个问题,对应业务系统来说,sql操作是非常多的,如果每次都需要加上这个条件,这就变的非常麻烦,而且也增加了很多测试的工作,有没有办法简化呢?mybatisplus 框架提供了多租户的功能。它可以做到后面加上租户的查询条件,不需要人工加上 tenant_id 的查询条件。自动帮你在sql。

mybatisplus 多租户配置

多租户实体类。

@Data
public class TenantDto {

    private Integer tenantId;
}

把租户id注入spring容器

@Configuration
public class TenantConfig {

    @Bean
    public TenantDto setTenant(){
        TenantDto tenantDto = new TenantDto();
        tenantDto.setTenantId(1);
        return tenantDto;
    }
}

mybatisplus 配置

@Configuration
@MapperScan("com.example.mybatisplustenant.mapper")
public class MybatisPlusConfig {

    @Autowired
    private TenantDto tenantDto;

    /**
     * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {

                return new LongValue(tenantDto.getTenantId());
            }

            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                return !"sys_user".equalsIgnoreCase(tableName);
            }
        }));
        // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
        // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
//        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }


}

用户实体类

@Data
@Accessors(chain = true)
@TableName("sys_user")
public class User {
    private Long id;
    /**
     * 租户 ID
     */
    private Long tenantId;
    private String name;

    @TableField(exist = false)
    private String addrName;

}

mapper 方法

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);
}
<?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.example.mybatisplustenant.mapper.UserMapper">

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

    <select id="getUserAndAddr" resultType="com.example.mybatisplustenant.entity.User">
        select u.id, u.name, a.name as addr_name
        from sys_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.example.mybatisplustenant.entity.User">
        select a.name as addr_name, u.id, u.name
        from user_addr a
        left join sys_user u on u.id=a.user_id
        <where>
            <if test="name!=null">
                a.name like concat(concat('%',#{name}),'%')
            </if>
        </where>
    </select>
</mapper>

测试,动态设置tenantId 可以动态的在sql 后面增加条件,达到多租户的效果。

    @Autowired
    private UserMapper mapper;

    @Autowired
    private TenantDto tenantDto;



    @Test
    public void aInsert() {
        tenantDto.setTenantId(123);
        User user = new User();
        user.setName("一一");
        Assertions.assertTrue(mapper.insert(user) > 0);
        user = mapper.selectById(user.getId());
        Assertions.assertTrue(123 == user.getTenantId());
    }

如果在代码中不想使用多租户的查询,也可以用下面的方法进行忽略

TenantHelper.ignore(() -> xxxx )

或者直接忽略不需要使用多租户的表。

public interface TenantLineHandler {

    /**
     * 获取租户 ID 值表达式,只支持单个 ID 值
     * <p>
     *
     * @return 租户 ID 值表达式
     */
    Expression getTenantId();

    /**
     * 获取租户字段名
     * <p>
     * 默认字段名叫: tenant_id
     *
     * @return 租户字段名
     */
    default String getTenantIdColumn() {
        // 如果该字段你不是固定的,请使用 SqlInjectionUtils.check 检查安全性
        return "tenant_id";
    }

    /**
     * 根据表名判断是否忽略拼接多租户条件
     * <p>
     * 默认都要进行解析并拼接多租户条件
     *
     * @param tableName 表名
     * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
     */
    default boolean ignoreTable(String tableName) {
        return false;
    }
}

4.总结

Sass 平台多租户的实现有多种,对于普通项目来讲,使用表字段进行数据隔离比较常见。对于业务开发来说,可以使用mybatisplus 的多租户功能进行开发,减少开发的工作量。

5.相关链接

表字段多租户项目
https://gitee.com/yangzheng1/mybatisplus-tenant

mybatisplus 多租户文档
https://baomidou.com/pages/aef2f2/

6.往期精彩推荐



 国内免费使用chatGPT的方法

数据库实时同步方案

推荐一款开源的大屏报表项目

欢迎点赞转发给我鼓励~

Sass多租户的4种实现方案,简单易懂,附上源码Sass多租户的4种实现方案,简单易懂,附上源码关注我获取更多IT分享 Sass多租户的4种实现方案,简单易懂,附上源码Sass多租户的4种实现方案,简单易懂,附上源码

原文始发于微信公众号(小羊架构):Sass多租户的4种实现方案,简单易懂,附上源码

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/260029.html

(0)
Java朝阳的头像Java朝阳

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!