SAAS简介

【简介引自】Spring Boot实现SAAS平台的基本思路
一、SAAS什么

 SaaS是Software-as-a-service(软件即服务)它是一种通过Internet提供软件的模式,厂商将应用软件对立部署在本人的服务器

   上,客户能够依据本人理论需要,通过互联网向厂商定购所需的应用软件服务,按定购的服务多少和工夫长短向厂商领取费用,

 并通过互联网取得厂商提供的服务。用户不必再购买软件,而改用向提供商租用基于Web的软件,来治理企业经营流动,且无需

 对软件进行保护,服务提供商会全权治理和保护软件。

二、SAAS模式有哪些角色

 ①服务商:服务商次要是治理租户信息,依照不同的平台需要可能还须要统合整个平台的数据,作为大数据的根底。服务商在SAAS

 模式中是提供服务的厂商。

 ②租户:租户就是购买/租用服务商提供服务的用户,租户购买服务后能够享受相应的产品服务。当初很多SAAS化的产品都会划分

 零碎版本,不同的版本凋谢不同的性能,还有基于性能免费之类的,不同的租户购买不同版本的零碎后享受的服务也不一样。

   三、SAAS模式有哪些特点

①独立性:每个租户的零碎互相独立。

②平台性:所有租户归平台对立治理。

③隔离性:每个租户的数据互相隔离。

在以上三个个性外面,SAAS零碎中最重要的一个标记就是数据隔离性,租户间的数据齐全独立隔离。

四、数据隔离有哪些计划

①独立数据库

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

   长处:

   为不同的租户提供独立的数据库,有助于简化数据模型的扩大设计,满足不同租户的独特需要,如果呈现故障,复原数据比较简单。

   毛病:

   增多了数据库的装置数量,随之带来保护老本和购买老本的减少。 如果定价较低,产品走高价路线,这种计划个别对运营商来说是无奈接受的。

②共享数据库,隔离数据架构

   即多个或所有租户共享数据库,然而每个租户一个Schema。

   长处:

   为安全性要求较高的租户提供了肯定水平的逻辑数据隔离,并不是齐全隔离,每个数据库可反对更多的租户数量。

   毛病:

   如果呈现故障,数据恢复比拟艰难,因为复原数据库将牵涉到其余租户的数据 如果须要跨租户统计数据,存在肯定艰难。

③共享数据库,共享数据架构

   即租户共享同一个数据库、同一个Schema,但在表中减少TenantID多租户的数据字段。这是共享水平最高、隔离级别最低的模式。 

   长处:

   三种计划比拟,第三种计划的保护和购买老本最低,容许每个数据库反对的租户数量最多。

毛病:

隔离级别最低,安全性最低,须要在设计开发时加大对平安的开发量,数据备份和复原最艰难,须要逐表逐条备份和还原。

如果心愿以起码的服务器为最多的租户提供服务,并且租户承受就义隔离级别换取降低成本,这种计划最适宜。

基于spring boot 、Mybatis-Plus实现动静切换数据源实现多租户SaaS零碎

注: 该模块基于此我的项目 搭建Springboot我的项目并集成Mybatis-Plus 的环境根底上,进行搭建部署。

创立数据库表

创立 people_config 表构造如下:

其对应的数据库 Schema 脚本如下:

DROP TABLE IF EXISTS `people_config`;CREATE TABLE `people_config`  (  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键Id',  `empl_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '职员Id',  `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号',  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '姓名',  `area_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '区域Id',  `area_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '区域名称',  `dept_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '部门Id',  `dept_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '部门名称',  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '提交工夫',  `create_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',  `create_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人Id',  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '批改工夫',  `update_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '批改人',  `update_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '批改人_id',  `is_delete` int NULL DEFAULT NULL COMMENT '是否删除',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

其对应的数据库 Data 脚本如下:

DELETE FROM people_config;INSERT INTO `mybatis_plus`.`tbl_people_config`(`id`, `empl_id`, `phone`, `name`, `area_id`, `area_name`, `dept_id`, `dept_name`, `create_time`, `create_user`, `create_id`, `update_time`, `update_user`, `update_id`, `is_delete`) VALUES (1, '职员Id 1', '17343759359', 'isWulongbo', '波波区域', '1区块链', '1', '开发部', '2020-12-05 15:12:00', '波波', '1', NULL, NULL, NULL, 0);INSERT INTO `mybatis_plus`.`tbl_people_config`(`id`, `empl_id`, `phone`, `name`, `area_id`, `area_name`, `dept_id`, `dept_name`, `create_time`, `create_user`, `create_id`, `update_time`, `update_user`, `update_id`, `is_delete`) VALUES (2, '职员Id 2', '13549553864', 'isWulongtao', '涛涛区域', '2区块链', '2', '军区部', '2020-12-05 16:07:59', '涛涛', '2', NULL, NULL, NULL, 0);

创立 tenant_info 表构造如下:

其对应的数据库 Schema 脚本如下:

DROP TABLE IF EXISTS `tenant_info`;CREATE TABLE `tenant_info`  (  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',  `tenant_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '租户id',  `tenant_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '租户名称',  `datasource_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据源url',  `datasource_username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据源用户名',  `datasource_password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据源明码',  `datasource_driver` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '数据源驱动',  `system_account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '零碎账号',  `system_password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '账号密码',  `system_project` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '零碎PROJECT',  `status` int NULL DEFAULT NULL COMMENT '是否启用(1是0否)',  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创立工夫',  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新工夫',  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

其对应的数据库 Data 脚本如下:

DELETE FROM tenant_info;INSERT INTO `tenant_info` VALUES (1, '1', 'test1', 'jdbc:mysql://127.0.0.1:3306/mybatis_plus?serverTimezone=Asia/Shanghai&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull', 'root', 'root', 'com.mysql.cj.jdbc.Driver', 'baba', '123456', '治理', 1, '2020-12-05 14:50:50', NULL);INSERT INTO `tenant_info` VALUES (2, '2', 'test2', 'jdbc:mysql://云服务器ip:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai', '云服务器数据库连贯账号', '云服务器数据库连贯明码', 'com.mysql.cj.jdbc.Driver', 'baba2', '123456', 'admin', 1, '2020-12-05 15:06:38', NULL);INSERT INTO `tenant_info` VALUES (3, '3', 'test3', 'jdbc:mysql://127.0.0.1:3306/mybatis_plus_one?serverTimezone=Asia/Shanghai&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull', 'root', 'root', 'com.mysql.cj.jdbc.Driver', 'baba3', '123456', 'root', 1, '2020-12-05 16:16:21', NULL);

生成mybatisplus的相干代码

执行 GeneratorCodeConfig 并输出表名:people_config tenant_info

回车后,目录构造如下:

留神TenantInfo表: 数据库 datetime 类型的会映射为 LocalDateTime咱们将其批改为 Date类型

外围代码

在该我的项目中 新建一个 package 命名为:datasource
创立 动静数据源 DynamicDataSource

package com.mybatis.plus.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;import java.util.Map;/** * 动静数据源 * * @author 吴龙波 * @version 1.0 * @Description 动静数据源 * @date 2020/12/5 23:26 */public class DynamicDataSource extends AbstractRoutingDataSource {    /** * 如果不心愿数据源在启动配置时就加载好,能够定制这个办法,从任何你心愿的中央读取并返回数据源 * 比方从数据库、文件、内部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可 * @return */ @Override protected DataSource determineTargetDataSource() {        return super.determineTargetDataSource(); }    /** * 如果心愿所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个办法 * @return */ @Override protected Object determineCurrentLookupKey() {        return DynamicDataSourceContextHolder.getDataSourceKey(); }    /** * 设置默认数据源 * @param defaultDataSource */ public void setDefaultDataSource(Object defaultDataSource) {        super.setDefaultTargetDataSource(defaultDataSource); }    public void setDataSources(Map<Object, Object> dataSources) {        super.setTargetDataSources(dataSources); // TODO 将数据源的 key 放到数据源上下文的 key 汇合中,用于切换时判断数据源是否无效 DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet()); }}

创立动静数据源切面拦挡 DynamicDataSourceAspect

package com.mybatis.plus.datasource;import com.mybatis.plus.base.BaseResponse;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.context.annotation.EnableAspectJAutoProxy;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;/** * 动静数据源 * * @author 吴龙波 * @version 1.0 * @Description 动静数据源切面拦挡 * @date 2020/12/5 23:26 */@Slf4j@Aspect@Component@Order(1) // 请留神:这里order肯定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,能力获取到最终的数据源@EnableAspectJAutoProxy(proxyTargetClass = true)public class DynamicDataSourceAspect {    @Around("execution(* com.mybatis.plus.controller.*.*(..)) || execution(* com.mybatis.plus.*.*(..))")    public Object doAround(ProceedingJoinPoint jp) throws Throwable {        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); Object result = null; try {            HttpServletRequest request = sra.getRequest(); HttpSession session = sra.getRequest().getSession(true); String tenantId = (String)session.getAttribute("tenantId"); if (StringUtils.isEmpty(tenantId)) {                tenantId = request.getParameter("tenantId"); }            log.info("以后租户Id:{}", tenantId); if (!StringUtils.isEmpty(tenantId)) {                DynamicDataSourceContextHolder.setDataSourceKey(tenantId); result = jp.proceed(); } else {                result = "查问失败,以后租户信息未取到,请分割技术专家!"; }        } catch (Exception ex) {            ex.printStackTrace(); result = "零碎异样,请分割技术专家!"; } finally {           DynamicDataSourceContextHolder.clearDataSourceKey(); }        return result; }}

创立动静数据源上下文 DynamicDataSourceContextHolder

package com.mybatis.plus.datasource;import org.apache.commons.lang3.StringUtils;import java.util.ArrayList;import java.util.Collection;import java.util.List;/** * 动静数据源 * * @author 吴龙波 * @version 1.0 * @Description 动静数据源上下文 * @date 2020/12/5 23:26 */public class DynamicDataSourceContextHolder {    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {        /** * 将 master 数据源的 key作为默认数据源的 key */ @Override protected String initialValue() {            return "master"; }    }; /** * 数据源的 key汇合,用于切换时判断数据源是否存在 */ public static List<Object> dataSourceKeys = new ArrayList<>(); /** * 切换数据源 * @param key 数据源 */ public static void setDataSourceKey(String key) {        if (!StringUtils.isEmpty(key)) {            contextHolder.set(key); }    }    /** * 获取数据源 * @return */ public static String getDataSourceKey() {        return contextHolder.get(); }    /** * 重置数据源 */ public static void clearDataSourceKey() {        contextHolder.remove(); }    /** * 判断是否蕴含数据源 * @param key 数据源 * @return */ public static boolean containDataSourceKey(String key) {        return dataSourceKeys.contains(key); }    /** * 增加数据源Keys * @param keys * @return */ public static boolean addDataSourceKeys(Collection<? extends Object> keys) {        return dataSourceKeys.addAll(keys); }}

创立动静数据源初始化DynamicDataSourceInit

package com.mybatis.plus.datasource;import com.mybatis.plus.entity.TenantInfo;import com.mybatis.plus.service.ITenantInfoService;import com.mybatis.plus.util.SpringContextUtils;import com.zaxxer.hikari.HikariDataSource;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.HashMap;import java.util.List;import java.util.Map;/** * 动静数据源 * * @author 吴龙波 * @version 1.0 * @Description 动静数据源初始化 * @date 2020/12/5 23:26 */@Slf4j@Configurationpublic class DynamicDataSourceInit {    @Autowired private ITenantInfoService tenantInfoService; @Bean public void initDataSource() {        log.info("======初始化动静数据源====="); DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringContextUtils.getBean("dynamicDataSource"); HikariDataSource master = (HikariDataSource) SpringContextUtils.getBean("master"); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("master", master); List<TenantInfo> tenantList = tenantInfoService.list(); for (TenantInfo tenantInfo : tenantList) {            log.info(tenantInfo.toString()); HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(tenantInfo.getDatasourceDriver()); dataSource.setJdbcUrl(tenantInfo.getDatasourceUrl()); dataSource.setUsername(tenantInfo.getDatasourceUsername()); dataSource.setPassword(tenantInfo.getDatasourcePassword()); dataSource.setDataSourceProperties(master.getDataSourceProperties()); dataSourceMap.put(tenantInfo.getTenantId(), dataSource); }        // 设置数据源 dynamicDataSource.setDataSources(dataSourceMap); /** * 必须执行此操作,才会从新初始化AbstractRoutingDataSource 中的 resolvedDataSources,也只有这样,动静切换才会起效 */ dynamicDataSource.afterPropertiesSet(); }}

创立MyBatisPlus配置MybatisPlusConfig

package com.mybatis.plus.datasource;import com.baomidou.mybatisplus.core.parser.ISqlParser;import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;import org.apache.ibatis.plugin.Interceptor;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * 动静数据源 * * @author 吴龙波 * @version 1.0 * @Description MyBatisPlus配置 * @date 2020/12/5 23:26 */@EnableTransactionManagement@Configuration@MapperScan({"com.mybatis.plus.mapper","com.mybatis.plus.*.*.mapper"})public class MybatisPlusConfig {    @Bean("master")    @Primary @ConfigurationProperties(prefix = "spring.datasource.hikari")    public DataSource master() {        return DataSourceBuilder.create().build(); }    @Bean("dynamicDataSource")    public DataSource dynamicDataSource() {        DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("master", master()); // 将 master 数据源作为默认指定的数据源 dynamicDataSource.setDefaultDataSource(master()); // 将 master 和 slave 数据源作为指定的数据源 dynamicDataSource.setDataSources(dataSourceMap); return dynamicDataSource; }    @Bean public MybatisSqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); /** * 重点,使分页插件失效 */ Interceptor[] plugins = new Interceptor[1]; plugins[0] = paginationInterceptor(); sessionFactory.setPlugins(plugins); //配置数据源,此处配置为要害配置,如果没有将 dynamicDataSource作为数据源则不能实现切换 sessionFactory.setDataSource(dynamicDataSource()); // 扫描Model sessionFactory.setTypeAliasesPackage("com.mybatis.plus.*.*.entity,com.mybatis.plus.entity"); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); // 扫描映射文件 sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml")); return sessionFactory; }    @Bean public PlatformTransactionManager transactionManager() {        // 配置事务管理, 应用事务时在办法头部增加@Transactional注解即可 return new DataSourceTransactionManager(dynamicDataSource()); }    @Bean public PaginationInterceptor paginationInterceptor() {        PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); List<ISqlParser> sqlParserList = new ArrayList<>(); // 攻打 SQL 阻断解析器、退出解析链 sqlParserList.add(new BlockAttackSqlParser()); paginationInterceptor.setSqlParserList(sqlParserList); return paginationInterceptor; }}

并在 application.properties 文件中配置默认数据源配置

spring.datasource.hikari.username=rootspring.datasource.hikari.password=rootspring.datasource.hikari.jdbc-url=jdbc:mysql://127.0.0.1:3306/mybatis_plus?serverTimezone=Asia/Shanghai&allowMultiQueries=true&zeroDateTimeBehavior=convertToNullspring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.hikari.connection-test-query = SELECT 1spring.datasource.hikari.idle-timeout = 30000spring.datasource.hikari.max-lifetime = 1880000spring.datasource.hikari.connection-timeout = 30000spring.datasource.hikari.minimum-idle = 5spring.datasource.hikari.validation-timeout = 60000

创立好后的目录构造如下:

因为咱们在 MybatisPlusConfig 回去扫 mapper 包 以及读取 application.properties 配置,所以切记咱们在启动类 MybatisPlusApplication中别再反复扫描DAO,并且要排除SpringBootApplication 的数据源的主动配置

综上所述:MybatisPlusApplication 批改如下:

package com.mybatis.plus;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//@MapperScan(basePackages = {"com.mybatis.plus"}) //扫描DAOpublic class MybatisPlusApplication {    public static void main(String[] args) {        SpringApplication.run(MybatisPlusApplication.class, args); }}

管制页面简略测试代码如下:

PeopleConfigController

package com.mybatis.plus.controller;import com.alibaba.fastjson.JSONObject;import com.mybatis.plus.base.BaseApiService;import com.mybatis.plus.base.BaseResponse;import com.mybatis.plus.entity.PeopleConfig;import com.mybatis.plus.service.IPeopleConfigService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;/** * <p> * 前端控制器 * </p> * * @author wulongbo * @since 2020-12-05 */@RestController@RequestMapping("/peopleConfig")public class PeopleConfigController extends BaseApiService {    @Autowired private IPeopleConfigService peopleConfigService; @GetMapping("/info")    public BaseResponse<List<PeopleConfig>> getInfo() {        List<PeopleConfig> list = peopleConfigService.list(); return setResultSuccess(list); }}

TenantInfoController

package com.mybatis.plus.controller;import com.mybatis.plus.base.BaseApiService;import com.mybatis.plus.base.BaseResponse;import com.mybatis.plus.datasource.DynamicDataSource;import com.mybatis.plus.entity.TenantInfo;import com.mybatis.plus.service.ITenantInfoService;import com.mybatis.plus.util.SpringContextUtils;import com.zaxxer.hikari.HikariDataSource;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;/** * <p> * 前端控制器 * </p> * * @author wulongbo * @since 2020-12-05 */@Slf4j@RestController@RequestMapping("/tenantInfo")public class TenantInfoController  extends BaseApiService {    @Autowired private ITenantInfoService tenantInfoService; @GetMapping("/info")    public BaseResponse<TenantInfo> getInfo() {        List<TenantInfo> list = tenantInfoService.list(); return setResultSuccess(list); }    @GetMapping("/save")    public BaseResponse<?> saveInfo() {        TenantInfo tenantInfo = new TenantInfo(); tenantInfo.setTenantName("动静新增"); tenantInfo.setDatasourceUrl("jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=Asia/Shanghai&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull"); tenantInfo.setDatasourceUsername("root"); tenantInfo.setDatasourcePassword("root"); tenantInfo.setDatasourceDriver("com.mysql.cj.jdbc.Driver"); tenantInfo.setStatus(1); tenantInfo.setCreateTime(new Date(System.currentTimeMillis())); tenantInfo.setUpdateTime(new Date(System.currentTimeMillis())); boolean b = tenantInfoService.save(tenantInfo); if (b) {            log.info("======初始化动静数据源====="); DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringContextUtils.getBean("dynamicDataSource"); HikariDataSource master = (HikariDataSource) SpringContextUtils.getBean("master"); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("master", master); List<TenantInfo> tenantList = tenantInfoService.list(); for (TenantInfo tenantInfos : tenantList) {                log.info(tenantInfos.getTenantId() + "     " + tenantInfos.getTenantName()); HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName(tenantInfos.getDatasourceDriver()); dataSource.setJdbcUrl(tenantInfos.getDatasourceUrl()); dataSource.setUsername(tenantInfos.getDatasourceUsername()); dataSource.setPassword(tenantInfos.getDatasourcePassword()); dataSource.setDataSourceProperties(master.getDataSourceProperties()); dataSourceMap.put(tenantInfos.getTenantId(), dataSource); }            // 设置数据源 dynamicDataSource.setDataSources(dataSourceMap); /** * 必须执行此操作,才会从新初始化AbstractRoutingDataSource 中的 resolvedDataSources,也只有这样,动静切换才会起效 */ dynamicDataSource.afterPropertiesSet(); }        return b ? setResultSuccess("初始化动静数据源胜利!") : setResultError("初始化动静数据源失败!"); }}

启动Springboot我的项目

启动 MybatisPlusApplication

应用 postman 拜访 localhost:8080/peopleConfig/info 须要咱们在Body上带人tenantId

咱们切换 tenantId 的值 为'1' ,'2','3' 查看返回值

因为工夫紧迫,后续再做具体阐明,明天到此为止。