basedaojava多数据源_SpringBoot构建多租户SaaS平台核⼼
技术指南
1. 概述
2. 尝试了解多租户的应⽤场景
3. 维护、识别和路由租户数据源
4. 项⽬构建
5. 实现租户数据源查询模块
6. 实现租户业务模块
7. 配置
8. 维护租户标识信息
9. 动态数据源切换
10. 应⽤测试
《Java 2019 超神之路》
《Dubbo 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》
《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》
《数据库实体设计合集》
《Java ⾯试题 —— 精品合集》
《Java 学习指南 —— 精品合集》
1. 概述
笔者从2014年开始接触SaaS(Software as a Service),即多租户(或多承租)软件应⽤平台;并⼀直从事相关领域的架构设计及研发⼯作。机缘巧合,在笔者本科毕业设计时完成了⼀个基于SaaS的⾼效财务管理平台的课题研究,从中收获颇多。最早接触SaaS时,国内相关资源匮乏,唯⼀有的参照资料是《互联⽹时代的软件⾰命:SaaS架构设计》(叶伟等著)⼀书。最后课题的实现是基于OSGI(Open Service Gateway Initiative)Java动态模块化系统规范来实现的。
时⾄今⽇,五年的时间过去了,软件开发的技术发⽣了巨⼤的改变,笔者所实现SaaS平台的技术栈也更新了好⼏波,真是印证了那就
话:“⼭重⽔尽疑⽆路,柳暗花明⼜⼀村”。基于之前⾛过的许多弯路和踩过的坑,以及近段时间有许多⽹友问我如何使⽤Spring Boot实现多租户系统,决定写⼀篇⽂章聊⼀聊关于SaaS的硬核技术。
说起SaaS,它只是⼀种软件架构,并没有多少神秘的东西,也不是什么很难的系统,我个⼈的感觉,SaaS平台的难度在于商业上的运营,⽽⾮技术上的实现。就技术上来说,SaaS是这样⼀种架构模式:它让多个不同环境的⽤户使⽤同⼀套应⽤程序,且保证⽤户之间的数据相互隔离。现在想想看,这也有点共享经济的味道在⾥⾯。
笔者在这⾥就不再深⼊聊SaaS软件成熟度模型和数据隔离⽅案对⽐的事情了。今天要聊的是使⽤Spring Boot快速构建独⽴数据库/共享数据库独⽴Schema的多租户系统。我将提供⼀个SaaS系统最核
⼼的技术实现,⽽其他的部分有兴趣的朋友可以在此基础上⾃⾏扩展。
2. 尝试了解多租户的应⽤场景
假设我们需要开发⼀个应⽤程序,并且希望将同⼀个应⽤程序销售给N家客户使⽤。在常规情况下,我们需要为此创建N个Web服务器(Tomcat),N个数据库(DB),并为N个客户部署相同的应⽤程序N次。现在,如果我们的应⽤程序进⾏了升级或者做了其他任何的改动,那么我们就需要更新N个应⽤程序同时还需要维护N台服务器。接下来,如果业务开始增长,客户由原来的N个变成了现在的N+M个,我们将⾯临N个应⽤程序和M个应⽤程序版本维护,设备维护以及成本控制的问题。运维⼏乎要哭死在机房了...
为了解决上述的问题,我们可以开发多租户应⽤程序,我们可以根据当前⽤户是谁,从⽽选择对应的数据库。例如,当请求来⾃A公司的⽤户时,应⽤程序就连接A公司的数据库,当请求来⾃B公司的⽤户时,⾃动将数据库切换到B公司数据库,以此类推。从理论上将没有什么问题,但我们如果考虑将现有的应⽤程序改造成SaaS模式,我们将遇到第⼀个问题:如果识别请求来⾃哪⼀个租户?如何⾃动切换数据源?
3. 维护、识别和路由租户数据源
我们可以提供⼀个独⽴的库来存放租户信息,如数据库名称、链接地址、⽤户名、密码等,这可以统⼀的解决租户信息维护的问题。租户的识别和路由有很多种⽅法可以解决,下⾯列举⼏个常⽤的⽅式:
可以通过域名的⽅式来识别租户:我们可以为每⼀个租户提供⼀个唯⼀的⼆级域名,通过⼆级域名就可以达到识别租户的能⼒,如ample,ample;tenantone和tenant就是我们识别租户的关键信息。
可以将租户信息作为请求参数传递给服务端,为服务端识别租户提供⽀持,如ample?
tenantId=ample?tenantId=tenant2。其中的参数tenantId就是应⽤程序识别租户的关键信息。
可以在请求头(Header)中设置租户信息,例如JWT等技术,服务端通过解析Header中相关参数以获得租户信息。
在⽤户成功登录系统后,将租户信息保存在Session中,在需要的时候从Session取出租户信息。
解决了上述问题后,我们再来看看如何获取客户端传⼊的租户信息,以及在我们的业务代码中如何使⽤租户信息(最关键的是DataSources的问题)。
我们都知道,在启动Spring Boot应⽤程序之前,就需要为其提供有关数据源的配置信息(有使⽤到数据库的情况下),按照⼀开始的需求,有N个客户需要使⽤我们的应⽤程序,我们就需要提前配置好N个数据源(多数据源),如果N<50,我认为我还能忍受,如果更多,这样显然是⽆法接受的。为了解决这⼀问题,我们需要借助Hibernate 5 提供的动态数据源特性,让我们的应⽤程序具备动态配置客户端数据源的能⼒。简单来说,当⽤户请求系统资源时,我们将⽤户提供的租户信息(tenantId)存放在ThreadLoacal中,紧接着获取TheadLocal中的租户信息,并根据此信息查询单独的租户库,获取当前租户的数据配置信息,然后借助Hibernate动态配置数据源的能⼒,为当前请求设置数据源,最后之前⽤户的请求。这样我们就只需要在应⽤程序中维护⼀份数据源配置信息(租户数据库配置库),其余的数据源动态查询配置。接下来,我们将快速的演⽰这⼀功能。
4. 项⽬构建
我们将使⽤Spring Boot 2.1.5版本来实现这⼀演⽰项⽬,⾸先你需要在Maven配置⽂件中加⼊如下的⼀些配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>        </dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.apachemons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
然后提供⼀个可⽤的配置⽂件,并加⼊如下的内容:
spring:
freemarker:
cache:false
template-loader-path:
- classpath:/templates/
prefix:
suffix:.html
resources:
static-locations:
- classpath:/static/
devtools:
restart:
enabled:true
jpa:
database:mysql
show-sql:true
generate-ddl:false
hibernate:
ddl-auto:none
una:
master:
datasource:
url:jdbc:mysql://localhost:3306/master_tenant?useSSL=false
username:root
password:root
sql.jdbc.Driver
maxPoolSize:10
idleTimeout:300000
minIdle:10
poolName:master-database-connection-pool
logging:
level:
root:warn
org:
springframework:
web:debug
java核心技术有哪些hibernate:debug
由于采⽤Freemarker作为视图渲染引擎,所以需要提供Freemarker的相关技术
una:master:datasource配置项就是上⾯说的统⼀存放租户信息的数据源配置信息,你可以理解为主库。接下来,我们需要关闭Spring Boot⾃动配置数据源的功能,在项⽬主类上添加如下的设置:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
publicclass UnaSaasApplication {
public static void main(String[] args) {
SpringApplication.run(UnaSaasApplication.class, args);
}
}
最后,让我们看看整个项⽬的结构:
5. 实现租户数据源查询模块
我们将定义⼀个实体类存放租户数据源信息,它包含了租户名,数据库连接地址,⽤户名和密码等信息,其代码如下:
@Data
@Entity
@Table(name = "MASTER_TENANT")
@NoArgsConstructor
@AllArgsConstructor
@Builder
publicclass MasterTenant implements Serializable{
@Id
@Column(name="ID")
private String id;
@Column(name = "TENANT")
@NotEmpty(message = "Tenant identifier must be provided")
private String tenant;
@Column(name = "URL")
@Size(max = 256)
@NotEmpty(message = "Tenant jdbc url must be provided")
private String url;
@Column(name = "USERNAME")
@Size(min = 4,max = 30,message = "db username length must between 4 and 30")
@NotEmpty(message = "Tenant db username must be provided")
private String username;
@Column(name = "PASSWORD")
@Size(min = 4,max = 30)
@NotEmpty(message = "Tenant db password must be provided")
private String password;
@Version
privateint version = 0;
}