Schema based multitenancy with SQLServer and Hibernate
Asked Answered
W

2

6

I am interested at implementing a multitenancy solution using hibernate as provider and SQLServer as database. I am using the schema based approach meaning one database and different schemas.

Specifically, my question is how to alter the execution schema with SQLServer. I have used this approach with MySQL use $database, PostgreSQL SET search_path TO $schema and Oracle ALTER SESSION SET CURRENT_SCHEMA = $schema and i was able to successfully switch from one schema to an other, noting that the schema concept differs from one database to another.

However, with SQLServer, I understand that there's no such thing as altering execution schema. I am aware of the possibility of switching the default schema of a given user in case the user is not granted the sysadmin role. I am also aware of the impersonation concept using the folllwing query : EXECUTE AS USER = $user. Impersonation allows one user to impersonate an other user, which gives the possibility to change the current user.

Switching the current user allows by definition to switch the execution schema since once the user is changed the execution schema is the new user's default schema. However, the impersonation has a limitation since we can't perform impersonation over 32 times.Although it's possible to execute revert after each impersonation in order to avoid reaching the limit impersonation number, this solution is not suitable in my case and I'm looking for an alternative.

Does anyone have any suggestions about multitenancy implementation using SQLServer and a schema based approach?

Also is there any solutions other than the ones I mentioned to switch execution schema.

Your help is very much appreciated.Thank you so much.

Wreck answered 12/9, 2017 at 9:56 Comment(1)
Hi.. Got any solution for this?Wyckoff
H
2

The following are the points for consideration

  1. The SQL Server differentiates the schema's by the prefix in the tables like [dbo].[Users] and [Tenant01].[Users]
  2. Since you have not mentioned the way in which you resolve the database connections for each schema in SQL Server, I would like to suggest that you can take a look at Azure Shard Map which supports your use-case or build your own in which case you would pickup the connection string from a centralized store based on the Tenant Context established. For ex: Tenant01 will be mapped to a ConnectionString C01 which will have the same database but with a different userid and password. This approach is the one that is more commonly followed. However you can also opt for Azure Shard Map which does this mapping behind the scenes for you.
  3. In this case, when you provision a schema, you also create a SQL User or map an Azure AD User to the schema and grant the necessary permissions on the Schema. This ensures that the access is fine.

The above option of persisting the tenant-wise connection strings in a store will be helpful in case you plan to scale out a specific tenant from the Shared DB due to considerable volume of data growth so as to offer higher response times and also better performance for the rest of the Tenants.

More on Azure Shard Map

EDIT

Actually, when you have a user mapped to a schema in SQL Server, you don't need to use the [Schema].[Table] in the Query, instead you can directly use the [Table], the access happens automatically. You can actually map the user to the schema using likeALTER USER erpadmin WITH DEFAULT_SCHEMA = erpadmin; from then on, the queries does not require the schema prefix for the table. More details here

HTH

Horsefaced answered 13/9, 2017 at 18:28 Comment(3)
Hi Saravanan, Thank you So much for the suggestions. I am aware of the prefix in the table name option but it's not what I need. Also i'm not looking for an approach to implement multitenancy from scratch, so the Azure Shard Map is not right for me. Do you have any suggestions about actually switching execution schema with SQLServer as I mentioned with other databases ? Thank you.Wreck
I have updated the post, kindly refer and let us know if this helps !Horsefaced
Thanks for the update. I requested switching the execution schema rather than the default schema as I already tried ALTER USER myUser WITH DEFAULT_SCHEMA = mySchema; . I didn't go with this alternative as it raises two concerns for me : sysadmin role and especially multithreading.Wreck
I
0

I have this issue too and my solution was to add a default schema at JPA config like:

 @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;
}

And Add a Hibernate query interceptor to replace the default scheme string on query.

@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;
        }
    };
}
Ihab answered 30/1, 2019 at 17:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.