In the last post I was discussing about using Hibernate for multiple datasources with no partitioning. The solution is quite simple with no big design changes. But in the case of partitioned databases you might need to do some changes to your DAL design.
The idea here is to use a datasource identifier to decide which partition to use.
public abstract class DataSourceIdentifier {
protected String dataSourceId;
public String getDataSourceId() {
if(dataSourceId == null) {
resolve();
}
return dataSourceId;
}
// Add the partitioning logic here
public abstract void resolve();
}
I just borrowed the idea of ThreadLocal from other posts to identify the current working datasource. But unlike the other solution (routing-data-source) referred in the last post, this would allow us to work with multiple datasources simultaneously.
public final class ContextHolder {
private ContextHolder holder;
private ThreadLocal context;
// Private, to make it singleton
private ContextHolder() {
context = = new ThreadLocal();
}
public ContextHolder getInstance() {
if(holder == null) {
holder = new ContextHolder();
}
return holder;
}
public DataSourceIdentifier getLastUsedDataSourceIdentifier() {
DataSourceIdentifier dataSourceId = (DataSourceIdentifier)context.get();
return dataSourceId;
}
public void use(DataSourceIdetifier dataSourceId) {
context.set(dataSourceId);
}
}
Now, we gotta implement our custom Spring HibernateTemplate class to make things work.
public class CustomHibernateTemplate extends HibernateTemplate {
public void setContext(DataSourceIdentifier dataSourceId) {
ContextHolder.getInstance().use(dataSourceId);
}
public T load(Class entityClass, Serializable id, DataSourceIdentifier dataSourceId) throws DataAccessException {
setContext(dataSourceId);
return super.load(entityClass, id);
}
// Override the super class methods and throw an exception as we need DatasourceIndetifier to work with a datasource.
public T load(Class entityClass, Serializable id) throws DataAccessException {
throw new IllegalArgumentException();
}
// Likewise you can do for all the super class methods
public SessionFactory getSessionFactory() {
DataSourceIdentifier dataSourceIdentifier = ContextHolder.getInstance().getLastUsedDataSourceIdentifier();
if(dataSourceIdentifier == null) {
// TODO get the default datasource (partition)
dataSourceIdentifier = getDefaultDataSourceIdentifier();
}
return getSessionFactory(dataSourceIdentifier);
}
protected SessionFactory getSessionFactory(DataSourceIdentifier dataSourceIdentifier) {
// TODO get from application context or do an dependency injection or whatever you like/know of
}
}
Thats it, we are almost done. We gotta use this template in the DAO class.
public class HibernateDAO extends HibernateDaoSupport {
public void delete(T entity, DataSourceIdentifier dataSourceId) throws DAException {
try {
getTemplate().delete(entity, dataSourceId);
} catch (DataAccessException e) {
throw new DAException(e);
}
}
public void refresh(T entity, DataSourceIdentifier dataSourceId) throws DAException {
try {
getTemplate().refresh(entity, dataSourceId);
} catch (DataAccessException e) {
throw new DAException(e);
}
}
@Override
protected HibernateTemplate createHibernateTemplate(SessionFactory factory) {
return new CustomHibernateTemplate(sessionFactory);
}
public CustomHibernateTemplate getTemplate() {
return (CustomHibernateTemplate) getHibernateTemplate();
}
}
Make sure all your subclasses of HibernateDAO calls getTemplate() instead of getHibernateTemplate() as the later will return an HibernateTemplate and you would have to cast it to CustomHibernateTemplate. So better to use getTemplate() in the DAO.
If you go with container managed transactions, you can sit back and relax, nothing to worry about. But for those poor guys like me who don’t use JTA, we need to customize the transaction manager as well.
public class CustomHibernateTransactionManager extends HibernateTransactionManager {
public void setContext(DataSourceIdentifier dataSourceId) {
ContextHolder.getInstance().use(dataSourceId);
}
public void getTransaction(TransactionDefinition definition, DataSourceIdentifier dataSourceId) {
setContext(dataSourceId);
super.getTransaction(definition);
}
// Unfortunately, we can't override the super class method getTransaction(definition) as its final ;-(.
// But there's a workaround for that as well around. I'm not going to confuse you explaining it here.
public SessionFactory getSessionFactory() {
DataSourceIdentifier dataSourceIdentifier = ContextHolder.getInstance().getLastUsedDataSourceIdentifier();
if(dataSourceIdentifier == null) {
// TODO get the default datasource (partition)
dataSourceIdentifier = getDefaultDataSourceIdentifier();
}
return getSessionFactory(dataSourceIdentifier);
}
protected SessionFactory getSessionFactory(DataSourceIdentifier dataSourceIdentifier) {
// TODO get from application context or do an dependency injection or whatever you like/know of
}
}
Yep.. We are done with all the fixes (Hacks ???). Even though we save the dataSourceId in the threadLocal, we should still be able to work with more than one partitions within the same thread provided you use Spring’s HibernateTemplate in your code.
What if you want to use the same connection to connect to all the partitions?. Meaning, all the partitions are there in the same database instance and you want switch the partitions by changing the catalog. I shall discuss this in my next post.
