Hibernate 6 Shared schema Multitenancy


Shared schema (or row-level) multitenancy means that the data of all tenants is stored in one database (schema). Depending on your use case this might be OK or totally unacceptable due to for example data protection concerns. It is harder to delete all data of a specific Tenant. Also creating backups per tenant is not possible. But on the other hand it makes it easier to back up all tenants at once since all data is in one database (schema).
The code
This article assumes that you use Spring Boot. For this to work in other frameworks one Hibernate configuration property needs to be set. More on that later on.
To persist the information to which Tenant a row belongs a new column needs to be added to a database table. To do this we need to use the Hibernate 6 annotation @TenantId
on a String field.
import org.hibernate.annotations.TenantId;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column
@TenantId
String tenantId;
@Column
String firstName;
@Column
String lastName;
@Column
Integer age;
}
Most likely we want every Entity to be multi-tenant aware. In this case, it is a good idea to create a BaseEntity class with the @MappedSuperclass
annotation and put the @TenantId
there. After that, every entity should inherit from this BaseEntity class.
The second and last step is to configure Hibernate to determine the current tenant. To do this we need to implement the CurrentTenantIdentifierResolver
interface.
import java.util.Map;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TenantResolver implements CurrentTenantIdentifierResolver,
HibernatePropertiesCustomizer{
@Override
public String resolveCurrentTenantIdentifier() {
// get the tenant from a HTTP Header, a HTTP Session, etc...
return "tenant1";
}
@Override
public boolean validateExistingCurrentSessions() {
return false;
}
@Override
public void customize(Map<String, Object> hibernateProperties) {
hibernateProperties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, this);
}
}
In the code above in the method resolveCurrentTenantIdentifier()
we need to return the current tenant. Depending on the type of our application this will most likely be from an HTTP Header or the HTTP Session, but it can be really from anywhere.
The Spring Boot specific part in the code above is the HibernatePropertiesCustomizer
interface and the customize()
method. This allows us to directly set this class as the tenant resolver for hibernate. Important: annotate this class with Spring's @Configuration
annotation.
In other frameworks than Spring Boot or even in plain Spring Framework we need to set the Hibernate property "hibernate.tenant_identifier_resolver"
to the name of the class implementing the CurrentTenantIdentifierResolver
interface. In our case this is TenantResolver
. Don't forget to prepend the class name with the package in which this class is.
Testing
Saving the above Employee Entity like this:
public void saveNewEntity() {
Employee newEmp = new Employee();
newEmp.setAge(22);
newEmp.setFirstName("John");
newEmp.setLastName("Doe");
employeeRepository.save(newEmp);
}
Results in the following SQL generated and executed:
Hibernate:
/* insert for
com.example.demo.employee.Employee */
insert into
employee (age,first_name,last_name,tenant_id)
values
(?,?,?,?)
Note that the tenant_id was automatically added by Hibernate. In the DB the record looks like this:
Now when selecting data a WHERE clause with the tenant_id column is automatically added to every SQL statement generated.
Hibernate:
/* SELECT
emp
FROM
Employee emp */ select
e1_0.id,
e1_0.age,
e1_0.first_name,
e1_0.last_name,
e1_0.tenant_id
from
employee e1_0
where
e1_0.tenant_id = ?
One important caveat:
Native SQL queries are not automatically filtered by tenant id; you’ll have to do that part yourself.
For more detailed info as always go to the documentation: https://docs.jboss.org/hibernate/orm/6.3/introduction/html_single/Hibernate_Introduction.html#multitenancy
Summary
Row-level multitenancy is the easiest variant of multi-tenancy to implement in Hibernate
It is available from Hibernate 6
It offers less data protection because all data of all tenants is stored in one database (schema).
Backups per tenant are not possible
Deleting all data of a tenant is harder
Backup of all tenants at once is obviously easier
Subscribe to my newsletter
Read articles from Robert Niestroj directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Robert Niestroj
Robert Niestroj
Software Developer doing Java, JavaScript for over 10 years. Working in Polish and German projects.