Using Hibernate to Implement Multi-tenant Cloud Architecture

Using Hibernate to Implement Multi-tenant Cloud Architecture

Multi-tenant cloud architecture is a way to partition data such that a single instance of an application can host data from multiple organizations simultaneously. A multi-tenant application allows each organization (tenant) to co-exist without compromising the security of data defined for other.

While the concept is intriguing to many SaaS developers, a full-featured, multi-tenant-aware framework hasn’t emerged in the cloud computing world yet. Hibernate — being a widely used Java ORM tool — provides some of the features required to implement multi-tenant cloud architecture but not all (more are slated for its forthcoming version). Until then, without specific API support, let’s see how you can leverage the limited Hibernate support to implement a multi-tenant cloud architecture.

This article first gives an overview of multi-tenancy in the cloud and then explains how you can use Hibernate to implement that architecture.

Multi-tenant Cloud Architecture and Database Design

The two main considerations for a multi-tenant cloud architecture are the structure of the schema/database and the method for storing the record. There are three approaches for implementing multi-tenant cloud architecture through database design:

  1. Multiple-tenant, multiple database: In this approach each tenant has their own physical database instance.

  2. Shared database, multiple schema per tenant: In this approach all tenants share a physical database but have an individual schema or catalog within the database instance.

  3. Shared database, shared schema: In this approach every tenant shares a single table to hold its data. Each tenant’s data is uniquely identified by a discriminator value called tenant-id.

Each of the models above has its own advantages and disadvantages, but models 1 and 2 arguably do not adhere to the concept of multi-tenant cloud architecture at all. In particular, model 2 provides only a portion of multi-tenant cloud architecture features. That leaves model 3 as the best option.

Multi-tenancy with Hibernate

In Hibernate, you can implement model 3 in the following two ways, each with its own benefits and shortcomings.

  1. Multi-tenant cloud architecture based on separate schema
    • The problem of selecting a unique key does not arise.
    • Any ID can be unanimously selected as the primary key of the table.
    • A custom ConnectionProvider enables you to route calls to the correct JDBC connection.
    • This approach is unsafe with second-level cache.
    • Applications are not aware of tenant-id.
    • Database backup would be easy (for each tenant separately).
  2. Discriminator-based multi-tenant cloud architecture
    • A particular field in a database table called tenant ID is used to uniquely identify a tuple.
    • By this ID, you know which row belongs to which tenant.
    • Applications have to make sure that only relevant rows of the database are filtered out while manipulating data through query.
    • The problem of selecting a unique key arises.
    • This approach is safe with second-level cache.
    • Database backup would be difficult if not impossible (for each tenant separately).

Multi-tenancy with Hibernate: Separate Schema Model

The model for a separate schema-based multi-tenant cloud architecture is simple to implement. PostgreSQL provides some excellent features for schema separation, which is useful in this scenario. In PostgreSQL, schema is like the namespace for the database. Access to schema with full qualified tenant_id (which in this case is schema name) can be used to separate one schema from the other. As an example, consider this simple SQL specific to PostgreSQL.

CREATE SCHEMA tenant1;CREATE SCHEMA tenant2;/*CREATE TABLE mytable; this will create table in public schema. We do not want do that */CREATE TABLE tenant1.mytable(some_id varchar);CREATE TABLE tenant2.mytable(some_id varchar);/*INSERT INTO mytable VALUES('111'),('222'); we do not want to do this either */INSERT INTO tenant1.mytable VALUES('111'),('222');INSERT INTO tenant2mytable VALUES('111'),('222');SELECT * FROM tenant1.mytable;SELECT * FROM tenant2.mytable;

Hibernate can handle this by telling the Session which tenant it belongs to, and consequently it can be handled by ConnectionProvider because it may provide a contract for obtaining JDBC connections. Steve Ebersole (Lead Developer, Hibernate) provided some examples on how to do this:

Session s = …;s.setTenant ("tenant 1");

Or you could pass tenant 1 along to the openSession call and then to the ConnectionProvider, or maybe pass the Session itself.

  1. public interface ConnectionProvider {public Connection getConnection(String tenant);}
  2. public interface ConnectionProvider {public Connection getConnection(Session session);}
  3. public interface ConnectionProvider {public static interface ConnectionOptions {public String getTenant();}public Connection getConnection(ConnectionOptions options);}

Hibernate does not offer much direct API support in implementing the first model, but it is equipped to handle multi-tenancy. The features of PostgreSQL combined with Hibernate do make things easier, which may make this approach attractive.

Multi-tenancy with Hibernate: Discriminator-based Model

In this model, a particular field in a database table called tenant ID is used to uniquely identify a particular tenant. It is by this ID that you know which row belongs to which tenant. The application has to make sure that only relevant rows of the database are filtered out while manipulating data through query. Support for this method is planned for Hibernate 4+.

However, with the help of filters, you can implement discriminator-based multi-tenant cloud architecture today. Below is the complete code for accomplishing this. is the POJO class mapped to the database. The only non-specific addition is the field tenantId, which acts as the discriminator to make your class multi-tenant aware in database persistence.

package org.multitenant.dto;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.Id;import org.hibernate.annotations.Filter;import org.hibernate.annotations.FilterDef;import org.hibernate.annotations.Filters;import org.hibernate.annotations.ParamDef;@Entity@FilterDef(name="tenantFilter", parameters=@ParamDef(name="tenantId", type="string"))@Filters(@Filter(name="tenantFilter", condition="tenant_id=:tenantId"))public class Product{@Idprivate long id;@Column(name="tenant_id", nullable=false, updatable=false)private String tenantId;@Column(name="prod_name", nullable=false)private String prodName;public long getId() {return id;}public void setId(long id) { = id;}public String getTenantId() {return tenantId;}public void setTenantId(String tenantId) {this.tenantId = tenantId;}public String getProdName() {return prodName;}public void setProdName(String prodName) {this.prodName = prodName;}}

The file simulates a request interceptor. Observe that all sessions are enabled to tenant base Hibernate filter.

package org.multitenant.dto;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.hibernate.cfg.Configuration;import org.hibernate.service.ServiceRegistry;import org.hibernate.service.ServiceRegistryBuilder;public class Main{public static void main(String[] args){Configuration configuration = new Configuration();configuration.configure();ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);Session session=sessionFactory.openSession();session.enableFilter("tenantFilter").setParameter("tenantId", "TID1");session.beginTransaction();Product p1=new Product();p1.setId(222);p1.setTenantId("TID1");p1.setProdName("Lollypop");session.persist(p1);session.getTransaction().commit();session.close();session=sessionFactory.openSession();session.enableFilter("tenantFilter").setParameter("tenantId", "TID1");session.beginTransaction();Product p2=(Product)session.createQuery("from Product").uniqueResult();session.getTransaction().commit();System.out.println("Tenant ID: "+p2.getTenantId());System.out.println("ID : "+p2.getId());System.out.println("Product Name : "+p2.getProdName());session.close();}}

Hibernate.cfg.xml contains the usual configuration; nothing particular to multi-tenancy here.

The Security Question

Providing data security is a big concern when implementing multi-tenant cloud architecture through Hibernate. To what level data are secure in a multi-tenant architecture implemented through discriminator is something to investigate. User concern about how secure their data are has been one of the burning issues in cloud computing. The level of security that should be provided by the database vendor versus the level that should be implemented with Hibernate or in the application level is a topic of debate. Hibernate does not yet have much direct API support in this arena either. However, Hibernate is an excellent (if not the most user-friendly) Java ORM tool. Developers like me are really eager for enhancements in multi-tenant cloud architecture features.


Share the Post: