SaaS是Software-as-a-Service(软件即服务)的简称,这边具体的解释不介绍。多租户的系统可以应用这种模式的思想,将思想融入到系统的设计之中。

现在SaaS Multi-Tenant在数据存储上存在两大类共三种主要的方案,分别是:独立数据库和共享数据库,其中共享数据库又可分为共享数据库,隔离数据架构和共享数据库,共享数据架构。 具体如下:

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

共享数据库,隔离数据架构, 即多个或所有租户共享Database,但一个Tenant一个Schema。

共享数据库,共享数据架构, 即租户共享同一个Database、同一个Schema,但在表中通过TenantID区分租户的数据。这是共享程度最高、隔离级别最低的模式。

1.下面简单谈谈基于JPA实现上述的第二种方案:创建数据库demo,表名tb_user,tb_user_1,tb_user_2,tb_user_3。表结构如下图所示:

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-%E8%A1%A8%E7%BB%93%E6%9E%84.png图1-1 表结构

2.使用经典的三层架构(3-tier application)开发web应用,此时完成了最最最普通的查询与保存功能。如下图所示:

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-application%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6-1024x407.png图2-1 application.properties

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-controller.png图2-2 controller

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-service.png图2-3 service

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-dao.png图2-4 dao

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-model.png图2-5 model

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-%E7%8A%B6%E6%80%81%E6%9E%9A%E4%B8%BE.png图2-6 状态枚举

3.通过1和2两个步骤我们已经可以正常的查询和保存数据了,但是全都保存在了tb_user表中,实际上我们多租户系统是需要根据tenantId来将数据保存到不同的表中的。接下来就是重点。创建共享常量类用来保存需要分表的表名集合或数组。我们将表名写到配置文件中split.table.list,方便修改。

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-%E5%85%B1%E4%BA%AB%E5%B8%B8%E9%87%8F.png图3-1 共享常量

4.创建本地线程共享变量用来存放租户ID,接口请求时就是根据前端传的租户Id来区分是哪一个租户。这里还可以扩展一下,比如接口请求实际上花费了多少时间等等,将有用的字段定义一下。

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-%E6%9C%AC%E5%9C%B0%E7%BA%BF%E7%A8%8B%E5%85%B1%E4%BA%AB%E5%8F%98%E9%87%8F.png图4-1 本地线程共享变量

5.实现WebMvcConfigurer接口里的addInterceptors方法添加全局拦截器,在全局拦截器里我们拦截每一个请求,将租户ID存到我们的本地线程共享变量中。

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-WebMvcConfigurer.png图5-1 WebMvcConfigurer

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-interceptor-1024x463.png图5-2 interceptor

6.这一步才是重中之重,划重点。继承EmptyInterceptor类重写onPrepareStatement方法,onPrepareStatement方法是在准备sql字符串时调用的,所以这里我们可以将其稍微改造一下。根据本地线程共享变量中的租户ID和需要分表的表名来修改对应sql语句,最后返回原始或修改过的sql。这样就以为大功告成了吗,错了,还需要再配置一下hibernate拦截器实现的包名路径:spring.jpa.properties.hibernate.ejb.interceptor=com.saas.demo.config.JpaInterceptor

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-hibernate%E6%8B%A6%E6%88%AA%E5%99%A8.png图6-1 hibernate拦截器

经过上面6个步骤,我们已经实现了JPA分表的功能,调用一下接口http://127.0.0.1:10002/user/users查看数据,Http消息头(Http Headers)里添加tenantId值作为租户的区分。看看结果:

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-%E7%BB%93%E6%9E%9C1-1024x462.png租户1结果

JPA%E5%AE%9E%E7%8E%B0SaaS%E5%A4%9A%E7%A7%9F%E6%88%B7%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8-%E7%BB%93%E6%9E%9C2.png租户2结果

租户1(tb_user_1)事先插入了一条数据,租户2(tb_user_2)没有数据。ok,大功告成,收工。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐