基于模式的多租户与SQLServer和Hibernate

时间:2017-09-12 09:56:09

标签: sql-server hibernate jpa database-schema multi-tenant

我对使用hibernate作为提供程序和SQLServer作为数据库实现多租户解决方案很感兴趣。 我使用的是基于模式的方法,意味着一个数据库和不同的模式。

具体来说,我的问题是如何使用SQLServer更改执行模式。我在MySQL use $database中使用过这种方法, PostgreSQL SET search_path TO $schema和Oracle ALTER SESSION SET CURRENT_SCHEMA = $schema和我能够成功地从一个模式切换到另一个模式,注意到模式概念因数据库而异。

然而,使用SQLServer,我知道没有改变执行模式的事情。我知道在未授予用户sysadmin角色的情况下切换给定用户的默认架构的可能性。我也知道使用folllwing查询的模拟概念:EXECUTE AS USER = $user。模拟允许一个用户模仿其他用户,这样就可以更改当前用户。

切换当前用户允许按定义切换执行模式,因为一旦用户被更改,执行模式就是新用户的默认模式。然而,模仿有一定的局限性,因为我们无法进行32次模拟。虽然每次模仿后都可以执行revert以避免达到极限模拟次数,但此解决方案并不适用于我的情况和我正在寻找替代方案。

有没有人对使用SQLServer和基于模式的方法的多租户实现有任何建议?

除了我提到的切换执行模式之外,还有其他解决方案。

非常感谢你的帮助。非常感谢你。

2 个答案:

答案 0 :(得分:2)

以下是需要考虑的要点

  1. SQL Server通过表格中的前缀区分模式,如[dbo]。[Users]和[Tenant01]。[Users]
  2. 由于您没有提到解析SQL Server中每个架构的数据库连接的方式,我建议您可以查看Azure Shard Map,它支持您的用例或构建您自己的用例在哪种情况下,您将根据建立的租户上下文从集中存储中获取连接字符串。例如:Tenant01将映射到ConnectionString C01,它将具有相同的数据库,但具有不同的用户ID和密码。这种方法更常被遵循。但是,您也可以选择Azure Shard Map,它可以在幕后为您进行此映射。
  3. 在这种情况下,在配置架构时,还可以创建SQL用户或将Azure AD用户映射到架构,并为架构授予必要的权限。这可以确保访问正常。
  4. 如果由于大量数据增长而计划从共享数据库中扩展出特定租户,以便提供更高的响应时间,那么上述在商店中持久保存租户连接字符串的选项将会很有帮助。其他租户的表现更佳。

    Azure Shard Map上的

    More

    修改

    实际上,当您将用户映射到SQL Server中的架构时,您不需要在查询中使用[Schema].[Table],而是可以直接使用[Table],访问自动发生。实际上,您可以使用类似ALTER USER erpadmin WITH DEFAULT_SCHEMA = erpadmin;的方式将用户映射到架构,查询不需要表的架构前缀。更多详情here

    HTH

答案 1 :(得分:0)

我也有这个问题,我的解决方案是在JPA配置中添加一个默认架构,例如:

 @Bean()
public LocalContainerEntityManagerFactoryBean entityManager() {
    Map<String, Object> jpaProperties = new HashMap<>();

    jpaProperties.put("exclude-unlisted-classes", true);
    jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, "org.hibernate.dialect.SQLServer2012Dialect");

    jpaProperties.put(org.hibernate.cfg.Environment.DRIVER, env.getProperty("ENTIDADES_PACKAGE"));
    **jpaProperties.put(org.hibernate.cfg.Environment.INTERCEPTOR, this.hibernateInterceptor());**
    **jpaProperties.put(org.hibernate.cfg.Environment.DEFAULT_SCHEMA, "dbo");**

    LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
    lef.setPersistenceUnitName("SECONDARY_DATABASE_PU");
    lef.setDataSource(secondaryDataSource());
    lef.setPersistenceProvider(new HibernatePersistenceProvider());
    lef.setJpaPropertyMap(jpaProperties);
    lef.setPackagesToScan("br.com.application.model");

    return lef;
}

并添加一个Hibernate查询拦截器以替换查询中的默认方案字符串。

@Bean
public Interceptor hibernateInterceptor() {
    return new EmptyInterceptor() {
        @Override
        public String onPrepareStatement(String sql) {
            String prepedStatement = super.onPrepareStatement(sql);

            return (TenantContext.getCurrentTenant() != null)
                    ? prepedStatement.replaceAll("dbo.", String.format("%s.", TenantContext.getCurrentTenant()))
                    : prepedStatement;
        }
    };
}