【问题标题】:Best practice to apply a universal criteria for multi-tenency in Spring JPA?在 Spring JPA 中应用多租户通用标准的最佳实践?
【发布时间】:2020-04-07 11:24:13
【问题描述】:

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

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

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

现在对于自动创建的存储库的“findAll”查询,它们也将返回其他客户的数据。

比如我现在有这样的界面

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

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

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

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


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

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


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

【问题讨论】:

  • 您是否使用共享模式来实现多租户?您如何区分每个客户
  • @secretsuperstar 它是通过一个名为 site_id 的列,它基本上是客户 ID

标签: spring-boot spring-data-jpa hibernate-criteria


【解决方案1】:

Spring Data JPA 在这里无能为力。通常您正在寻找Hibernate Multitenancy。具体来说,您正在寻找discriminator column multitenancy。但我认为即使是我最新的 Hibernate 版本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 {

【讨论】:

  • 非常感谢您指出正确的方向。我决定使用 DB-per-tenant 多租户策略,这种策略得到了更好的支持。
【解决方案2】:

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

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

因此,您可以创建将表名和 where 条件作为参数的查询构建器(如果您的列没有相同的名称,则这样做);并且为避免将来混淆,请为方法选择一个好的名称,例如 findAllByTableIn(String tableName, List in)

另请注意,您必须在目标 DataSource 上配置 JDBCTemplate,这将是您的租户之一,以访问正确数据库上的表。

【讨论】:

  • 这仍然需要覆盖我实现的每个方法。有没有办法做到一次并在任何地方应用它?例如,利用 spring AOP 功能为这些查询添加拦截器谓词。
  • 请注意,使用 in 有一些限制,具体取决于数据库。例如在 oracle 中,您不能超过 in 中的值超过 1000
  • 然后你可以使用 jdbcTemplate 并创建自定义的通用 DAO
  • @MohamedSweelam 这将需要创建很多 DAO。最好的方法是支持 @Where 注释中的函数调用,例如 @Where(clause = "site_id in myapp.SessionSiteId.getId()")
  • 请再读一遍我的回答,你只需要一个 DAO 就可以做到这一点。
猜你喜欢
  • 2012-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 2011-11-24
  • 1970-01-01
  • 2012-09-11
  • 1970-01-01
相关资源
最近更新 更多