Vishnu Vardhana Reddy
2 min readSep 3, 2018

Muti Tenant — With discriminator column : Hibernate implementation

Initial plan is to have single tenant application but plan has changed over time and decided to have multi tenant model. After considering all the options we have decided to go with the Partitioned (discriminator) data .

It requires changes in all entities to include the tenant id , queries has to be modified and have to do it in multiple service . We are using the hibernate JPA as entity framework along with spring(boot) in application.

We have started exploring the hibernate support on the multi tenant model and found out that it supports schema and data base but not supporting the partitioned (discriminator) approach. (https://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch16.html , https://hibernate.atlassian.net/browse/HHH-6054)

We have started exploring on the hibernate and found out we can use 2 of the feature hibernate supports one is interceptors and others is filter , using these 2 we can add common code.

Adding Tenant ID in CRUD :

We have created one tenant class which is extended by all entities . Base class will have tenant id column and filter definition .

@FilterDef(name = TENANT_FILTER, parameters = @ParamDef(name = TENANT_ID_PARAM, type = "int"))
@Filters(
@Filter(name = TENANT_FILTER, condition = TenantEntity.TENANT_ID+" = :"+TENANT_ID_PARAM)
)
@MappedSuperclass
@EntityListeners(TenantEntityListener.class)
public class TenantEntity implements Serializable {

public static final String TENANT_ID = "TENANT_ID";
@Column(name = TENANT_ID, columnDefinition = "INT")
private int tenantId;

public int getTenantId() {
return tenantId;
}

public void setTenantId(int tenantId) {
this.tenantId = tenantId;
}
}

Add One entity listener which will add tenant id before any curd operation

public class TenantEntityListener {
@PrePersist
@PreUpdate
@PreRemove
private void setTenantId(Object object) {
if(object instanceof TenantEntity){
((TenantEntity) object).setTenantId(123);
}
}
}

Adding Tenant id to Query :

Add Aspect on create entity manger method of EntityManager , before returning session we will add tenant details dynamically

Definition from entity class : @FilterDef(name = TENANT_FILTER, parameters = @ParamDef(name = TENANT_ID_PARAM, type = "int"))@Aspect
@Component
public class EnableFilterAspect {

@AfterReturning(
pointcut = "bean(pmsStayEntityManager) && execution(* createEntityManager(..))",
returning = "retVal")
public void getSessionAfter(JoinPoint joinPoint, Object retVal) throws Exception {
if (retVal != null && EntityManager.class.isInstance(retVal) ) {
addTenantFilter((EntityManager) retVal);
}
}

public static void addTenantFilter(EntityManager entityManager) {
if(!ObjectUtils.isEmpty(TenantContextHolder.getTenantInfo())
&& !ObjectUtils.isEmpty(TenantContextHolder.getTenantInfo().getTenantId())) {
Session session = entityManager.unwrap(Session.class);
session.enableFilter(TENANT_FILTER).setParameter(TENANT_ID_PARAM, TenantContextHolder.getTenantInfo().getTenantId()).validate();
}
}

}

It worked perfectly with spring boot and single entity manger but it failed if we have multiple entity mangers . After looking in to the spring transaction management classes have found out that JpaTrnsactionManger uses the native entity manger impl (not proxy) so our proxy didnt executed so we are not able to add any tenant id . I have created new JpaTrnsactionManger where i have overriden the create entity manager transaction method to add tenant details .

public class JpaTransactionManager extends org.springframework.orm.jpa.JpaTransactionManager {

protected EntityManager createEntityManagerForTransaction() {
EntityManager entityManager = super.createEntityManagerForTransaction();
EnableFilterAspect.addTenantFilter(entityManager);
return entityManager;
}
}

Responses (2)