Home Discriminator based Multi Tenancy using Hibernate Filter
Post
Cancel

Discriminator based Multi Tenancy using Hibernate Filter

In this post, We will learn how to implement the shared database & shared schema approach to provide multi-tenancy in a simple spring boot REST API. We will also talk about Spring AOP, Hibernate Filter and Reflection from time to time. This approach has been turned into a framework and it will be enough to add it to your project as a dependency.

There are 3 approaches for implementing multitenancy:

  1. Database Per Tenant: Each Tenant has its own database and is isolated from other tenants.
  2. Shared Database, Separate Schema: All Tenants share a database, but have their own database schemas and tables.
  3. Shared Database, Shared Schema: All Tenants share a database and tables. Every table has a Discriminator Column called Tenant Identifier, that is specifying the owner of the row. ← This project focuses on this option.

In order to separate data between different tenants, we use a Discriminator Column in every table to hold the tenant information for each row in the table.

Why Shared Database, Shared Schema ? 

★ Development, maintainability and operational costs do not depend on the number of tenants.

★ Tenant shared data is easy to maintain.

Application Design — A Naive Approach

The simplest approach with dealing with such an issue is that the whole application is aware that it is serving multiple tenants. In such an approach, you are passing the tenant id or tenant object through all layers of your application. The proposed naive approach is not good. All layers are aware of the Tenant. Even worse, the service layer is here just to pass the information to the repository layer. It does nothing with this information. It is just carrying to the repository layer.

Giving initiative to the developer in this work in a distributed environment causes an error prone structure. For this reason, we will ensure that it is managed by the framework we have published.

Application Design — An Advanced Approach

In the advanced approach, we are going to use goodies provided by Hibernate and Spring to create an application design in which only Security Context is going to be aware that we are serving multiple tenants. Other layers are not going to be aware that the application is serving multiple tenants.

Deep Into the Project ..

Highlights:

  • Discriminator based Multi Tenancy based on Hibernate Filter & Hibernate Interceptor.
  • WSO2 as identity provider and using the TenantId(Organization field from JWT) as discriminator.
  • Full integration with Spring JPA (Repository, JpaRepository, CrudRepository, …)
  • Integrated with WSO2 identity provider which automatically resolves the tenant id by using the received JWT token.

Multi-Tenancy:

Let’s look at the descriptions of all classes in the library we created to provide multi-tenancy.

  • TenantAssistance: Helper class to grab the SecurityContextHolder and extract the WSO2 tenant ID from the JWT token which is used in the TenantInterceptor. We use the organization field returned in JWT token as the tenant id.
  • TenantInterceptor: An implementation that automatically adds the tenantId to the entities that extend TenantEntity.
  • HibernateMultiTenancyInterceptorBean: Spring @Bean configuration to use the TenantInterceptor that autowired in HibernateInterceptorConfiguration.
  • HibernateInterceptorConfiguration: Configuration to tell hibernate to use the EmptyInterceptor @Bean as a session interceptor to intercept operations like save, update, delete, etc.
  • TenantEntity: JPA entity with @Filter annotation to automatically add a where clause around all repositories with tenantId as input (Due to TenantServiceAspect).
  • TenantServiceAspect: Spring AOP implementation to enable the @Filter automatically on every method of a Spring Data repository. It unwraps the current Session and enables the filter with tenantId as param.
  • MultiTenancyRepository: Custom repository base implementation that overrides default findById() method with CriteriaBuilder to avoid direct fetching. The method delete() is also overridden here because it is used internally em.find() in SimpleJpaRepository which creates a backdoor to the multi-tenancy structure. The method getOne() throws an EntityNotFoundException as otherwise, it bypasses the filter as it returns a proxy of the entity.

Direct Fetching & CriteriaBuilder

Filters apply to entity queries, but not to direct fetching.
Therefore, all methods that use em.find() do not take the filter into consideration when fetching an entity from the Persistence Context.

When using Spring Data you should know which methods of SimpleJpaRepository are using direct fetching (em.find()) and which ones are using (getQuery() -> CriteriaBuilder). The trick is the f_ilters apply only to entity queries, not to direct fetching.
Therefore, all methods that use em.find() do not consider the filter when fetching an entity from the Persistence Context.

The class MultiTenancyRepository already takes care of this problem completely !_

— GetOne ‼

The JpaRepository getOne() method uses the em.getReference() internally.
This will return an entity proxy which only has the primary key field initialized. Other fields are unset unless we lazily request them through getters.
The proxy does not have support for the filter thus we are avoiding this issue all together by throwing a EntityNotFoundException which is allowed by JPA.

  • MultiTenancyProperties: By setting the path containing the repository classes to this property file, we decide in which classes we will use the reflection feature.
  • NoMultiTenancyConfiguration: Repository classes that have NoMultiTenancyRepository annotation are found with the reflection feature. In the future, we may not want to apply the multitenancy structure for some entities. so we need these components to prevent the hibernate filter mechanism from running on these entities’ repository.
  • NoMultiTenancyEntity: It is placed on top of entities that will not be included in the multitenancy feature.
  • NoMultiTenancyRepository: It is placed on top of repositories that will not be included in the multitenancy feature.

OAUTH2 / Spring Security

  • WebSecurityConfig: Spring Security Web config that uses WSO2 as a OAuth2.0 resource server. Through autowiring, this is integrated in the SecurityContextHolder.
  • AudienceValidator: OAuth2Token validator which validates the audience.

USAGE

To summarize in a developer friendly, understandable way..

☞ Create Spring Boot Application.

Spring Initializr

☞ Add maven dependency to your pom.xml file.

☞ Add @EnableJpaRepositories(repositoryBaseClass = MultiTenancyRepository.class) to head of your spring boot startup class.

☞ Override dfx.online.data.repository.scan.path in your application properties file according to the package name where the repository classes are located.

☞ Inherit entity classes with the TenantEntity class from the library.

This is a our lightweight solution to provide Multitenancy(Shared database, shared schema) with Spring Boot application. Hope this will help anyone who wants to learn more about SaaS and Multi-Tenancy concepts. I am just trying to share my experiences and providing solutions for the issues observed during my learning. Please feel free to review or correct the details mentioned in this article which will help me to create more valuable contents for other developers. Happy Learning! ☺

References

OAuth 2.0 Resource Server JWT When using Spring Boot

Hibernate ORM 5.2.18.Final User Guide

This post is licensed under CC BY 4.0 by the author.