当前位置: 移动技术网 > IT编程>开发语言>Java > 基于mybatis-plus实现的多租户架构

基于mybatis-plus实现的多租户架构

2020年09月29日  | 移动技术网IT编程  | 我要评论
整体概述多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性。多租户在数据存储上主要存在三种方案,独立数据库、共享数据库,独立Schema、共享数据库,共享 Schema,共享数据表。独立数据库即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本较高。优点:为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;如果出现故.

整体概述

多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性。

多租户在数据存储上主要存在三种方案,独立数据库、共享数据库,独立Schema、共享数据库,共享 Schema,共享数据表。

独立数据库 

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

  • 优点:为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;如果出现故障,恢复数据比较简单。
  • 缺点:增多了数据库的安装数量,随之带来维护成本和购置成本的增加。

共享数据库,独立Schema、共享数据库

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

  • 优点:为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可支持更多的租户数量。
  • 缺点:如果出现故障,数据恢复比较困难,因为恢复数据库将牵涉到其他租户的数据;

方案选择

免费租户(体验租户)

免费租户或者体验租户,考虑到维护和购置成本,使用共享数据库,共享 Schema,共享数据表方案,通过tenant_id区分不同租户数据,实现数据隔离。

付费租户

收费租户为了获得更好的用户体验,结合MySql数据库,采用独立数据库方案,不同租户对应不同数据库,数据隔离级别最高,安全性最好。

租户升级

由免费租户(体验租户)升级到付费租户,不仅要为该租户创建独立数据库,还要同步历史数据,在不影响用户体验的情况,升级操作可以考虑异步定时处理。

技术选型

Mybatis-Plus简介

MyBatis-Plus(简称 MP)是一个  的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 多数据源切换数据源分组,适用于多种场景:纯粹多库、读写分离、一主多从、混合模式,提供对DruidMybatis-PlusP6syJndi的快速集成使用spel动态参数解析数据源,如从sessionheader或参数中获取数据源。

多租户SQL解析器

通过PaginationInterceptor拦截特定sql,加上tenant_id。具体代码如下:

@Bean
public PaginationInterceptor paginationInterceptor() {
    PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
    // SQL解析处理拦截:增加租户处理回调。
    List<ISqlParser> sqlParserList = new ArrayList<>();
    TenantSqlParser tenantSqlParser = new TenantSqlParser();
    tenantSqlParser.setTenantHandler(new TenantHandler() {
        @Override
        public Expression getTenantId(boolean where) {
            HttpServletRequest request = null;
            // 从当前系统上下文中取出当前请求的服务商ID,通过解析器注入到SQL中。
            if (null != RequestContextHolder.getRequestAttributes()) {
                request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            }
            Long currentTenantId;
            try {
                String tenantId;
                if (null == request) {
                    tenantId = TenantIdContext.peek();
                } else {
                    tenantId = request.getHeader("tenantId");
                    if (StringUtils.isBlank(tenantId)) {
                        HttpSession session = request.getSession();
                        tenantId = session.getAttribute(tenantId) == null ? null : session.getAttribute(tenantId).toString();
                    }
                    if (StringUtils.isBlank(tenantId)) {
                        tenantId = TenantIdContext.peek();
                    }
                }
                currentTenantId = Long.valueOf(tenantId);
            } catch (Exception e) {
                throw new RuntimeException("getTenantId error.");
            }
            return new LongValue(currentTenantId);
        }
        @Override
        public String getTenantIdColumn() {
            return SYSTEM_TENANT_ID;
        }
        @Override
        public boolean doTableFilter(String tableName) {
            // 这里可以判断是否过滤表
            return false;
        }
    });
    sqlParserList.add(tenantSqlParser);
    paginationInterceptor.setSqlParserList(sqlParserList);
    paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
        @Override
        public boolean doFilter(MetaObject metaObject) {
            /**
             * 如果在程序中,有部分SQL不需要加上租户ID的表示,需要过滤特定的sql
             */
            MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
            String msId = ms.getId();
            // 不是meeting下面的sql都不需要加租户ID
            if (msId.startsWith("com.hixiaoe.meeting.persistence.meeting.dao")) {
                return false;
            }
            return true;
        }
    });
    return paginationInterceptor;
}

初始化数据源

DynamicRoutingDataSource继承了AbstractRoutingDataSource抽象类,实现了InitializingBean, DisposableBean接口,内部维护了一个LinkedHashMap,用于存放当前所有数据源。通过DynamicDataSourceCreator创建未初始化的数据源,并添加数据源到当前数据源map中,以供动态切换使用,具体实现如下:

public static void addDataSource(String dsName, String username, String password, String driverName, String url) {
    DataSourceProperty dataSourceProperty = new DataSourceProperty();
    dataSourceProperty.setPollName(dsName);
    dataSourceProperty.setDriverClassName(driverName);
    dataSourceProperty.setUrl(url);
    dataSourceProperty.setUsername(username);
    dataSourceProperty.setPassword(password);
    DruidConfig druidConfig = new DruidConfig();
    druidConfig.setInitialSize(1);
    dataSourceProperty.setDruid(druidConfig);
    Map<String, DataSource> currentDataSources = dsUtil.dynamicRoutingDataSource.getCurrentDataSources();
    if (!currentDataSources.containsKey(dsName)) {
        DataSource ds = dsUtil.dynamicDataSourceCreator.createDruidDataSource(dataSourceProperty);
        dsUtil.dynamicRoutingDataSource.addDataSource(dsName, ds);
    }
}

数据源切换

使用 @DS ("dsName")切换数据源,@DS 可以注解在方法上和类上,同时存在,方法注解优先于类上注解。强烈建议只注解在service实现上,没有@DS,则采用默认数据源。dsName可以为组名也可以为具体某个库的名称(可以从session,header或参数中获取数据源)。

存在会话

http请求header中,或者httpsession设置tenantId,通过sql解析器在特定sql中处理对应租户数据。

异步线程

通过ThreadLocal设置当前线程tenantId,以供sql解析器在特定sql中处理对应租户数据。

整体架构图

注:以上架构来自狐小E智慧办公平台,企业数字化建设的全景攻略 https://www.hixiaoe.com

本文地址:https://blog.csdn.net/u011076500/article/details/108869045

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网