Saturday, September 19, 2015

Hibernate Multitenancy Implementation

Hi All,

After many efforts,I have implemented multitenant  with hibernate and Spring based using seperate schema approach.Iam providing the code for you.

If you need an introduction to hibernate multi-tenancy please refer to following hibernate documentation.

http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html


So starting with Context file to initialize your sessionfactory and datasoure

        class="org.springframework.orm.hibernate4.HibernateTransactionManager">     
 

class="in.sridhar.persistance.dao.SessionCurrentTenantIdentifierResolver">
   

            class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
        abstract="true"> destroy-method="close">
               

SessionCurrentTenantIdentifierResolver is used to resolve the current tenent during the establishment of connection to database.

Here Iam using ThreadLocal to identify my tenent id.

public class SessionCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver
{

    private static final Logger LOG = LoggerFactory
            .getLogger(SessionCurrentTenantIdentifierResolver.class);

    @Override
    public String resolveCurrentTenantIdentifier()
    {
        String tenantId = ThreadContext.getDataSourceIdentifier();
        System.out.println("Found TenantId=" + tenantId);
        return tenantId;
    }

    @Override
    public boolean validateExistingCurrentSessions()
    {
        return false;
    }

}


MultiTenantSchemaConnectionProvider  provides the implementation of getting and releasing conneciton  from data source and setting up the schema specific to tenant identifier



public class MultiTenantSchemaConnectionProvider implements MultiTenantConnectionProvider

{
    private static final long serialVersionUID = 4368575201221677384L;
    @Autowired
    private DataSource dataSource;

    @Override
    public boolean supportsAggressiveRelease()
    {
        return false;
    }

    @Override
    public boolean isUnwrappableAs(Class clazz)
    {
        return false;
    }

    @Override
    public T unwrap(Class clazz)
    {
        return null;
    }

    @Override
    public Connection getAnyConnection() throws SQLException
    {
        return dataSource.getConnection();
    }

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException
    {
        final Connection connection = getAnyConnection();
        try
        {
            System.out.println("inside sridharconnection :" + connection + " and tenantIdentifier "
                    + tenantIdentifier);
            connection.createStatement().execute("SET search_path to '" + tenantIdentifier + "'");
        }
        catch (SQLException e)
        {
            throw new HibernateException("Could not alter JDBC connection to specified schema ["
                    + tenantIdentifier + "]", e);
        }
        return connection;
    }

    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException
    {
        try
        {
            connection.createStatement().execute("SET search_path to 'public'");
        }
        catch (SQLException e)
        {
            throw new HibernateException(
                    "Could not alter JDBC connection to specified schema [public]", e);
        }
        connection.close();
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection)
            throws SQLException
    {
        releaseAnyConnection(connection);
    }

}

ThreadContext class has the the logic of implementing thread local

public class ThreadContext
{
    private static final ThreadLocal dataSourceIdentifier = new ThreadLocal();

    public static String getDataSourceIdentifier()
    {
        return dataSourceIdentifier.get();
    }

    public static void setDataSourceIdentifier(String contextStr)
    {
        dataSourceIdentifier.set(contextStr);
    }

    public static void resetDataSourceIdentifier()
    {
        dataSourceIdentifier.remove();
    }
}

Few things observed during the implementation of multi tenancy are:

1)hibernate.hbm2ddl.auto does work in multi tenancy approch
2) when you try to use


Session session = sessionFactory.withOptions() .tenantIdentifier( yourTenantIdentifier ) ... .openSession();

you will not able to use @transactional spring annotation.Because when we use this annotation spring internally uses session.getCurrentSession() which will not identify your tenent.

No comments:

Post a Comment