MULTI-TENANT APPLICATIONS USING SPRING BOOT, JPA, HIBERNATE AND POSTGRES
Multitenancy is an approach in which an instance of an application is used by different customers and thus dropping software development and deployment costs when compared to a single-tenant solution where multiple parts would need to be touched in order to provision new clients or update existing tenants.
There are multiple well known strategies to implement this architecture, ranging from highly isolated (like single-tenant) to everything shared.
Since the demo application is going to support multitenancy, the persistence layer needs to be manually configured similarly to any Spring application. It will consist of defining and configuring:
Hibernate, JPA and datasources properties.
Entity manager factory bean.
Transaction manager bean.
Spring Data JPA and transaction support (through the @Transactional annotation) configuration.
To accomplish so, let’s start with the Spring Boot application entry point to exclude a couple of Spring BootAutoConfiguration behavior, meaning the application would need to explicitly configure the datasources, Hibernate and JPA-related beans:
com.asimio.demo.config and com.asimio.demo.rest packages will be scanned for @Component-derived annotated classes.
Note: Excluding AutoConfiguration behavior mentioned earlier could also be accomplished through properties:
Simple JPA, Hibernate and the datasources configuration properties. No DDL will be generated or executed since the databases schema are already in place. The datasources are prefixed with multitenancy.dvdrental and read into a Java class attributes thanks to yaml support added to Spring but more on this next.
This is the Java class where all the JPA-related beans are instantiated. @Configuration specifies this class is going to provide @Bean-annotated methods defining beans that will be managed by the Spring container.
Also notice how JpaProperties and MultiTenantDvdRentalProperties instances are injected as a result of @EnableConfigurationProperties annotation in line 4.
JpaProperties is provided by Spring Boot and it will include configuration properties prefixed with spring.jpa as defined earlier.
MultiTenantDvdRentalProperties is a simple Java class as shown below, created for this demo and will include the properties prefixed with multitenancy.dvdrental, which is basically tenant information and datasource data to establish connections to the DB.
To make the entityManagerFactory bean multi-tenant-aware, its configuration properties need to include a multi-tenant strategy, a multi-tenant connection provider and a tenant identifier resolver implementations, that’s what have been configured in lines 26 through 28 along with the JPA properties defined in application.yml and explained here.
As for the multitenancy strategy, Hibernate supports:
A database per tenant.
A schema per tenant.
One or multiple table columns used to specify different tenants. Added in Hibernate 5
A requirement is not to set the datasource to the entityManagerFactory bean because it will be retrieved from the MultiTenantConnectionProvider and CurrentTenantIdentifierResolver implementations detailed next.
This MultiTenantConnectionProvider implementation uses the datasource Map discussed here to locate the expected datasource from the tenant identifier, which is retrieved from a CurrentTenantIdentifierResolver implementation reviewed next.
The CurrentTenantIdentifierResolver implementation used for this demo is a simple one delegating tenant selection to DvdRentalTenantContext static methods, which uses the ThreadLocal to store and retrieve tenant data.
One advantage of this approach instead of resolving tenant identifier using a request URL or HTTP Header is that the Repository layer could be tested without the need to start a servlet container.
THE TRANSACTION MANAGER BEAN
Once again, this is the Java class I have been reviewing where all the JPA-related beans are instantiated. The important things to notice here is that the txManager bean needs to unwrap the EntityManagerFactory implementation, Hibernate’s SessionFactory in this case, to set the AutodetectDataSource attribute to false, this is a requirement for multitenancy to work using the approach discussed in this post.
CONFIGURE SPRING DATA JPA AND ANNOTATION-DRIVEN TRANSACTIONS
Imported through @ImportResource annotation found in MultiTenantJpaConfiguration class, com.asimio.dvdrental.dao package includes the interfaces upon which Spring JPA Data instantiates the Repository (or Dao) beans.
tx:annotation-driven allows the execution of class methods annotated with @Transactional to be wrapped in a DB transaction without the need to manually handling the connection or transaction.
The REST layer is going to implement a Demo REST resource to demonstrate the multitenancy approach described in this post. It will consist of the REST resource, a Spring interceptor to select and set the tenant identifier and the configuration to associate the interceptor with the REST resource.
To keep this post and sample code simple I decided to inject the Repository dependency in the REST-related class, in a more serious or complex application, I would suggest to implement a Service class where one or more Dao dependencies would be used along with an Object Mapper / Converter to prevent leaking the model to the resource layer.
This Spring interceptor is going to use the ThreadLocal-based implementation included in DvdRentalTenantContext to set the tenant information passed though an HTTP Header. Another option would be to pass the tenant identifier in the URL or through a BEARER token. Although an interceptor was used for this post, a servlet filter could have been used instead.
This configuration is automatically done by Spring Boot but needed to be explicitly configured to associate the DvdRentalMultiTenantInterceptor interceptor with the REST requests.
RUNNING THE DEMO SERVICE
Sending a request to a /demo, resource implemented in the DemoResource class passing tenant information in X-TENANT-ID header:
Notice how the actor section in the response varies as a different tenant is passed in the X-TENANT-ID header for each request. Also worth mentioning is that the instance ids for the DemoResource and ActorDao instances are the same, meaning that even though multitenancy has been accomplished, they are still singleton instances for which the correct datasource is used.
In the next post I’ll cover Dynamic Configuration using Spring Cloud Bus and will look back at this entry to demo how updating the datasources list will automatically refresh the persistence layer with a new tenant, so stay tuned and sign up to the newsletter to receive updates from this blog. I hope you find this post informational and useful, thanks for reading and feedback is always appreciated.
Accompanying source code for this blog post can be found at: