I am currently trying to set up Hibernate for multi tenancy using the seperate Schema aproach.
After working on it for about 2 days now and browsing nearly every source
Ok to wrap this up, here is what I ended up with the following. I use a simple CurrentTenantIdentifierResolver. And Instead of trying to inject the DataSource from somewhere else to my MultiTenantConnectionProviderImpl I create the DataSource (c3p0 ComboPooledDatasource) in the ConnectionProvider and started using only the connections provided by the my ConnectionProvider. So I eliminated the extra DataSource. To make the properties of the DataSource easily configurable I opted to get the configuration data from a properties file.
CurrentTenantIdentifierResolverImpl:
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {
/**
* The method returns the RequestServerName as tenantidentifier.
* If no FacesContext is available null is returned.
*
* @return String tenantIdentifier
*/
@Override
public String resolveCurrentTenantIdentifier() {
if (FacesContext.getCurrentInstance() != null){
return FacesContext.getCurrentInstance().getExternalContext().getRequestServerName();
} else {
return null;
}
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
MultiTenantConnectionProviderImpl:
Note that the PropertyUtil is just a simple local helper class to fetch my properties. Since it is nothing special I won't include it to not clutter the answer.
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 8074002161278796379L;
private static Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class );
private ComboPooledDataSource cpds;
private Properties properties;
/**
*
* Constructor. Initializes the ComboPooledDataSource based on the config.properties.
*
* @throws PropertyVetoException
*/
public MultiTenantConnectionProviderImpl() throws PropertyVetoException {
log.info("Initializing Connection Pool!");
properties = new Properties();
try {
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
cpds = new ComboPooledDataSource("Example");
cpds.setDriverClass(properties.getProperty("jdbc.driver"));
cpds.setJdbcUrl(properties.getProperty("jdbc.url"));
cpds.setUser(properties.getProperty("jdbc.user"));
cpds.setPassword(PropertyUtil.getCredential("jdbc.password"));
log.info("Connection Pool initialised!");
}
@Override
public Connection getAnyConnection() throws SQLException {
log.debug("Get Default Connection:::Number of connections (max: busy - idle): {} : {} - {}",new int[]{cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){
log.error("Connection pool empty!");
}
return cpds.getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
log.debug("Get {} Connection:::Number of connections (max: busy - idle): {} : {} - {}",new Object[]{tenantIdentifier, cpds.getMaxPoolSize(),cpds.getNumBusyConnectionsAllUsers(),cpds.getNumIdleConnectionsAllUsers()});
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize()){
log.warn("Maximum number of connections opened");
}
if (cpds.getNumConnectionsAllUsers() == cpds.getMaxPoolSize() && cpds.getNumIdleConnectionsAllUsers()==0){
log.error("Connection pool empty!");
}
return cpds.getConnection(tenantIdentifier, PropertyUtil.getCredential(tenantIdentifier));
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection){
try {
this.releaseAnyConnection(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
@SuppressWarnings("rawtypes")
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProvider.class.equals( unwrapType ) || MultiTenantConnectionProviderImpl.class.isAssignableFrom( unwrapType );
}
@SuppressWarnings("unchecked")
@Override
public T unwrap(Class unwrapType) {
if ( isUnwrappableAs( unwrapType ) ) {
return (T) this;
}
else {
throw new UnknownUnwrapTypeException( unwrapType );
}
}
}
The c3p0 specific config is taken from the c3p0-config.xml:
3
SELECT 1
2000
30
1
18000
30
1
50
true
And the db specific properties are provided by a config.properties file:
jdbc.url=
jdbc.driver=
jdbc.dbName=
jdbc.dbowner=
jdbc.username=
jdbc.password=
hibernate.dialect=
hibernate.debug=false
The credentials are fetched in a similar fashion from another file.
Any feedback providing improvements is appreciated.