Blogg
Här finns tekniska artiklar, presentationer och nyheter om arkitektur och systemutveckling. Håll dig uppdaterad, följ oss på LinkedIn
Här finns tekniska artiklar, presentationer och nyheter om arkitektur och systemutveckling. Håll dig uppdaterad, följ oss på LinkedIn
In part 5, we implemented the Shared Database with Discriminator Column pattern using Hibernate filters. Since then, Hibernate has implemented native support for Discriminator-based multitenancy. Hence in this final part, we’ll implement the Shared Database with Discriminator Column pattern using Hibernate 6 and Spring Boot 3.
Support for the Shared Database with Discriminator Column pattern was for a long time described in the Hibernate documentation as “planned for the next version”. That forced us to use the Hibernate Filter mechanism together with some AspectJ magic to implement Shared Database with Discriminator Column. With Hibernate 6 (which is default in Spring Boot 3), the mechanism is finally available. The implementation uses a @TenandId
annotation in the entity classes, together with the usual CurrentTenantIdentifierResolver
strategy.
As usual, a fully working, minimalistic example can be found in the Github repository for this blog series, in the shared_database_hibernate6 branch.
First off, in order to use Hibernate 6 we need to upgrade the sample application to use Spring Boot 3. This is because Spring Boot 2.x uses the old javax.*
package names, whereas Hibernate 6 requires the new package names jakarta.*
.
Similar to the Database per Tenant and Schema per Tenant patterns, we need to provide an implementation of CurrentTenantIdentifierResolver
to instruct Hibernate how to get the current tenant for a request. The implementation is more or less identical to the other pattern implementations, except that we also need to implement HibernatePropertiesCustomizer
:
@Component
class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver, HibernatePropertiesCustomizer {
@Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getTenantId();
if (!ObjectUtils.isEmpty(tenantId)) {
return tenantId;
} else {
// Allow bootstrapping the EntityManagerFactory, in which case no tenant is needed
return "BOOTSTRAP";
}
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
@Override
public void customize(Map<String, Object> hibernateProperties) {
hibernateProperties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, this);
}
}
Setting the MULTI_TENANT_IDENTIFIER_RESOLVER
property reference is necessary, in order for the resolver implementation to be available to the EntityManager. One could argue that Hibernate should be able to retrieve the CurrentTenantIdentifierResolver
bean from the Spring BeanContainer
automatically, but that doesn’t currently work so the explicit config is necessary.
The next step is to decorate our Entity classes with a @TenantId
annotation to indicate a property to be used as tenant discriminator. We do that in our abstract Entity base class:
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public abstract class AbstractBaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Size(max = 30)
@Column(name = "tenant_id")
@TenantId
private String tenantId;
}
This is all Hibernate-specific configuration needed for implementing the Shared Database with Discriminator column pattern. It is indeed a much more elegent, robust and less intrusive implementation than the previous attempt (which used Hibernate Filters).
Remaining configuration in application.yml
is plain vanilla Spring Boot config:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/master
username: postgres
password: secret
jpa:
hibernate:
ddl-auto: none
open-in-view: false
liquibase:
enabled: true
changeLog: classpath:db/changelog/db.changelog-tenant.yaml
A fully working, minimalistic example can be found in the Github repository for this blog series, in the shared_database_hibernate6 branch.
Let’s test-drive the solution! Start the multi tenant service in a terminal windows.
Insert some test data for different tenants:
curl -H "X-TENANT-ID: tenant1" -H "Content-Type: application/se.callista.blog.service.api.product.v1_0+json" -X POST -d '{"name":"Product 1"}' localhost:8080/products
curl -H "X-TENANT-ID: tenant2" -H "Content-Type: application/se.callista.blog.service.api.product.v1_0+json" -X POST -d '{"name":"Product 2"}' localhost:8080/products
curl -H "X-TENANT-ID: tenant3" -H "Content-Type: application/se.callista.blog.service.api.product.v1_0+json" -X POST -d '{"name":"Product 3"}' localhost:8080/products
Then query for the data, and verify that the data is properly isolated between tenants:
curl -H "X-TENANT-ID: tenant1" localhost:8080/products
curl -H "X-TENANT-ID: tenant2" localhost:8080/products
curl -H "X-TENANT-ID: tenant3" localhost:8080/products
Having a look at the Product
table in the master
database shows our newly created test data, with discriminating tenant column:
That concludes this blog series. Thanks for reading!
The following link has been very useful inspiration when preparing this material: