Grails has a multi-tenancy plugin for a single database and a multi-tenancy plugin for multi-databases, but the one for multi-databases is no longer supported/maintained. Is there some way I can instead use Spring or Hibernate themselves for a multi-tenancy multi-database Grails application?
-
The answer is most likely. And it depends on what your requirements. Multi-tenancy isn't exactly the same definition for everyone. The more details about your requirements you can provide the better answer you can get. Take for instance, how do you need to identify your tenant on the request? Is it in the URL is it based on the host? Domain? Source IP? Selected by user at login? Associated with login? Central database for security and individual per tenant? Etc. – Joshua Moore May 05 '15 at 14:03
-
"Central database for security and individual per tenant?": Yes, Stormpath DB for security, and our own - probably individual - DB(s) for tenant info. "How do you need to identify your tenant on the request?": I was hoping to do it based on the username (i.e., "Associated with login"). Organization/tenancy would be selected upon signing up. If that's impossible, users could log in at a subdomain specific to their tenancy. – Daniel May 05 '15 at 14:56
-
The other question is how difficult would this be to do? Need to roll this out pretty quick so I'm considering using Django instead. – Daniel May 05 '15 at 14:57
3 Answers
You can use Hibernate multitenancy described here http://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch16.html
OR
You can also consider Spring's AbstractRoutingDataSource
The general idea is that a routing DataSource acts as an intermediary - while the ‘real’ DataSource can be determined dynamically at runtime based upon a lookup key.
https://spring.io/blog/2007/01/23/dynamic-datasource-routing/
You can find a newer post, giving an exemplary use with hibernate, the gist of the solution you can find in the following two snippets
public class MyRoutingDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
String language = LocaleContextHolder.getLocale().getLanguage();
System.out.println("Language obtained: "+ language);
return language;
}
}
the return value will be used as a discriminator for a datasource, the following configuration sets the mapping
<bean id="dataSource" class="com.howtodoinjava.controller.MyRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="en" value-ref="concreteDataSourceOne"/>
<entry key="es" value-ref="concreteDataSourceTwo"/>
</map>
</property>
</bean>

- 27,771
- 4
- 57
- 55
-
Interesting. I did not know about Spring DataSource Routing. I am going to read this. Maybe even try to make the example. If there would also be a way to add / remove dataSources while the application is running then this is amazing (at least for what I need). Thank you. Anyway, I don't think there is a way of doing this and keeping all the benefits of GORM. – ionutab May 19 '15 at 11:08
In our case we use LocalContainerEntityManagerFactoryBean where we create a multiTenantMySQLProvider.
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="packagesToScan" value="domain"/>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
<entry key="javax.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver" />
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.multiTenancy" value="SCHEMA" />
<entry key="hibernate.multi_tenant_connection_provider" value-ref="mySQLMultiTenantConnectionProvider" />
<entry key="hibernate.tenant_identifier_resolver" value-ref="tenantIdentifierResolver" />
</map>
</property>
</bean>
<bean id="tenantService"
class="multitenancy.service.impl.TenantServiceImpl">
<property name="defaultTenantId" value="${multitenancy.defaultTenantId}" />
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
<bean id="connectionProvider"
class="multitenancy.hibernate.ConnectionProviderImpl" lazy-init="false">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="mySQLMultiTenantConnectionProvider"
class="multitenancy.hibernate.MySQLMultiTenantConnectionProviderImpl" lazy-init="false">
<property name="connectionProvider" ref="connectionProvider" />
<property name="tenantIdentifierForAny" value="${multitenancy.tenantIdentifierForAny}" />
<property name="schemaPrefix" value="${multitenancy.schemaPrefix}" />
</bean>
<bean id="tenantIdentifierResolver"
class="multitenancy.hibernate.TenantIdentifierResolverImpl" lazy-init="false">
<property name="tenantService" ref="tenantService" />
</bean>
<bean id="tenantIdentifierSchedulerResolver"
class="security.impl.TenantIdentifierSchedulerResolverImpl" lazy-init="false">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
And here the implementation of the MySQLMultiTenantConnectionProviderImpl
public class MySQLMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, ServiceRegistryAwareService, Stoppable {
private static final Logger LOGGER = LoggerFactory.getLogger(MySQLMultiTenantConnectionProviderImpl.class);
@Setter
private ConnectionProvider connectionProvider;
@Setter
private String tenantIdentifierForAny;
@Setter
private String schemaPrefix;
@Override
public Connection getAnyConnection() throws SQLException {
return connectionProvider.getConnection();
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connectionProvider.closeConnection( connection );
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
String schema = schemaPrefix + tenantIdentifier;
try {
LOGGER.debug("setting schema in DB Connection : {}" , schema);
connection.createStatement().execute( "USE " + schema );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + schema + "]", e
);
}
return connection;
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.createStatement().execute( "USE " + tenantIdentifierForAny );
}
catch ( SQLException e ) {
LOGGER.error(" error on releaseConnection. The connection will be not closed. SQLException : {}" , e);
// on error, throw an exception to make sure the connection is not returned to the pool.
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e
);
}
// I follow the hibernate recommendation and we don't return the connetion to the pool.
connectionProvider.closeConnection( connection );
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
@Override
public void stop() {
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) ||
MultiTenantConnectionProvider.class.equals( unwrapType ) ||
AbstractMultiTenantConnectionProvider.class.isAssignableFrom( unwrapType );
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
throw new UnknownUnwrapTypeException( unwrapType );
}
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
}
}

- 12,873
- 23
- 91
- 153
This is how I use hibernate multitenancy with SCHEMA approach. May be it will be helpful for you.
applicationContext.xml
...
<bean id="multiTenantConnectionProvider" class="org.myapp.MyAppMultiTenantConnectionProvider"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="packagesToScan" value="org.myapp.entities"/>
<property name="multiTenantConnectionProvider" ref="multiTenantConnectionProvider"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.multiTenancy">SCHEMA</prop>
<prop key="hibernate.tenant_identifier_resolver">org.myapp.MyAppTenantIdentifierResolver</prop>
...
</props>
</property>
</bean>
...
MyAppMultiTenantConnectionProvider.java
public class MyAppMultiTenantConnectionProvider implements MultiTenantConnectionProvider {
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
//This is where your tenant resolving logic will be implemented
return MyMultitenantConnectionPool.getConnection(tenantIdentifier);
}
}
MyAppTenantIdentifierResolver.java
public class MyAppTenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
/*
This is where you determine which tenant to use.
In this app SpringSecurity used for this purpose.
TenantUser class extends org.springframework.security.core.userdetails.User with tenant information.
*/
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) return "";
if (authentication.getPrincipal() instanceof TenantUser) {
TenantUser user = (TenantUser) authentication.getPrincipal();
return user.getTenant();
} else return "";
}
@Override
public boolean validateExistingCurrentSessions() {
return false;
}
}

- 96
- 2
-
First of all I want to thank you. Second of all I must say that I do not have a good grasp on the concepts you have presented here and that I am a little confused. 1. How does the class "MyMultitenantConnectionPool" work. Is there any special kind of logic inside it or does it just instantiate the connection depending on the tenant ? 2. Is "getConnection" called on session instantiation or per request ? 3. Is there a public working demo for this ? – ionutab May 20 '15 at 08:04
-
1 & 2) Looks like getConnection() called per request, so "MyMultiTenantConnectionPool" must implement connection instantiation and adequate pooling strategy. Our application uses separate db to store information about tenants, tenant status, corresponding databases url and so on. At this point I have MyTenantManager to maintain this "meta" database. So, MyMultitenantConnectionPool retrieve db url from MyTenantManager and instantiate connections using according pooling startegy. 3) Sorry but this is a commercial project and I have no right to publish the source. – Lea32 May 20 '15 at 08:52
-
Thanks. Not sure what you mean by "schema approach"...? I'd think that you meant a shared database for tenants with a separate schema per tenant, but your comment seems to indicate otherwise. – Daniel May 20 '15 at 12:43
-
This: [Separate schema](http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html#d5e4754) – Lea32 May 20 '15 at 13:32
-