在 Spring JPA 中应用多租户通用标准的最佳实践?

Best practice to apply a universal criteria for multi-tenency in Spring JPA?

我正在构建具有多个租户(即客户)的企业应用程序 (SaaS)。相同功能的不同客户之间的数据存储在相同的 table 中,我使用一个名为 "site_id" 的列来定义数据的所有权。它看起来像这样:

PurchaseOrder:
- int id
- int site_id
- String product_name
- int quantity

在每个请求中,过滤器都会处理会话信息以确定该用户有权访问哪个站点。此数据存储在静态线程局部变量中,可以从名为 Set<Integer> RequestSiteScope.getSiteIds().

的静态方法中检索该变量

现在,对于那些 "findAll" 自动创建存储库的查询,它们也会 return 其他客户的数据。

比如我现在有这样一个界面

public interface PurchaseOrderRepository implements CrudRepository<PurchaseOrder, int> {
  List<PurchaseOrder> findAll();
}

我正在处理一个用户的请求,我知道该用户只能访问 site_id of 3,4。我想让 findAll 只有 return 数据使用 site_id in (3, 4) 标准。 SQL 应该看起来像带有参数 3, 4select * from purchase_order where site_id in (?, ?);

当然,我可以手动创建每个查询总是添加一个where site_id = ?子句,但这不仅乏味而且很容易被我未来的队友忘记。我查看了 @Query 注释,但它无济于事,因为我无法将动态变量 (site_id) 放入其中。

有没有办法改变 Spring 的逻辑,它负责神奇地实现这些存储库方法,这样我就可以在 where 子句中注入一条动态信息 (来自线程本地 class 静态变量)以编程方式?


这个概念有点像 Ruby 关于 Rails ActiveRecord scope 的概念,带有 lamda 的味道。理想情况下,所有涉及 table 和 "site_id" 的查询都将自动包含此条件,除非涉及某些特殊过程(功能块的注释禁用此)。

到目前为止,我一直在研究这些选项,但还没有决定结果:


更新:本文提供了Spring中的所有三种多租户解决方案:https://medium.com/swlh/multi-tenancy-implementation-using-spring-boot-hibernate-6a8e3ecb251a

Spring 为此类需求提供了两种解决方案,您可以在您的存储库中使用本机查询,并像这样向它发送绑定列表

public interface PurchaseOrderRepository implements CrudRepository<PurchaseOrder, int> {
    @Query(value = "select * from purchase_order where site_id in (?)", nativeQuery = true)
    List<PurchaseOrder> findAll(List<Integer> in);
}

或者,如果您正在寻找一种更动态的方式来构建您自己的可自定义查询,您可以使用 Spring JDBC 模板,它具有强大的可以完美处理您的情况的 API。使用它定义您自己的 DAO 并实现一些要调用的辅助方法。

检查这个很好的参考资料Spring JDBC Template Examples

因此您可以制作查询构建器,它将 table 名称和 where 条件作为参数(如果您没有相同的列名称);为了避免将来造成混淆,请为该方法选择一个好的名称,例如 findAllByTableIn(String tableName, List in)

另请注意,您必须在目标数据源上配置 JDBC 模板,该模板将成为您的租户之一,以便在正确的数据库上访问 table。

Spring Data JPA 在这里无能为力。通常您正在寻找 Hibernate Multitenancy. Specifically you are looking for discriminator column multitenancy. But i think even i latest Hibernate versions it's not yet implemented

或者,您可以使用 @Filter 注释推出自己的解决方案:

@FilterDef(
    name = "tenantFilter", 
    parameters = @ParamDef(name = "tenant", type = "int")
)
@Filter(
    name = "tenantFilter", 
    condition = "tenant_id = :tenant"
)
public class BaseEntity implements Serializable {