Hibernate - Dynamic Table Routing
I have been searching for a method to dynamically route objects to databases at runtime using Hibernate and recently I found a solution which fit the bill.
Join the DZone community and get the full member experience.
Join For FreeI have been searching for a method to dynamically route objects to databases at runtime using Hibernate and recently I found a solution which fit the bill. This post explores the problem and identifies one possible solution.
From http://www.alvinsingh.org/blog
The Problem
I have a web-application (using Spring+Hibernate for ORM) which makes use of two database schemas (in the same physical database). I specifiy a datasource accessible via JNDI (using tomcat achieved via context xml) so across my different deployment environments I can use the same war file. This is particularly important as I do not want to use different binaries for different environments. My ethos is to keep any dynamic information pertinent to the deployment environment strictly outside of the war file exactly for the above reason - same war file across deployments.
I specify one DS (datasource) since the spring hibernate session-factory expects a single DS. But this only gets my half way there - I have only specified one database connection string - what about the other database? You can have multiple session factories but then have to deal with cross session transactions.
We can solve this problem (not the main problem this post addresses) by using the ’schema’ and ‘catalog’ configuration items on our entities which are in the DB #2. Below is an example which I will use for reference involving 3 objects spanning 2 schemas. It involves a common practice where identity management information is kept in a separate database to the application(s) database. I have omitted most of the information from the objects for brevity.
@Table
@Entity
public class ApplicationUser {
private IdentityUser identityUser;
}
@Table (schema="secure", catalog="identitymanagement")
@Entity
public class IdentityUser {
@OneToMany(fetch = FetchType.LAZY)
@JoinTable(name = "IdentityUserIdentityRole",
schema="security",
catalog="identitymanagement",
joinColumns = {@JoinColumn(name = "IdentityUserID")},
inverseJoinColumns = {@JoinColumn(name = "IdentityRoleID")})
private Set<IdentityRole> roles;
}
@Table (schema="secure", catalog="identitymanagement")
@Entity
public class IdentityRole {
}
ApplicationUser is in DB #1 and is configured via my JNDI datasouce. Its schema and catalog can be configured via default hibernate properties. As can be seen, I have hard-coded the schema and catalog values for the identity management objects IdentityUser and IdentityUserRole. I cannot put dynamic elements in these annotations and also note that using hbm.xml files does not get around this (while also keeping a single binary across deployment environments). This is the core of the problem.
What this means is that I can have multiple instances of DB #1 and only a single instance of DB #2 (which must be named secure.identitymanagement.*) in any single deployment environment. Not very tenable.
The Solution
The way hibernate builds its queries is that it prepends the schema and catalog names when referring to the tables - i.e. in my example we would end up with a queries to the effect of - “select * from secure.identitymanagement.IdentityUser”. What we need is a way of manipulating this query building.
Enter the hibernate interceptor.
public class HibernateInterceptor extends EmptyInterceptor {
@Override
public String onPrepareStatement(String sql) {
String prepedStatement = super.onPrepareStatement(sql);
prepedStatement = prepedStatement.replaceAll("secure.identitymanagement", "my_dynamic_goodness");
return prepedStatement;
}
}
This interface is probably one of the most powerful hibernate features in the framework. It allows you to get at the core of Hibernate and manipulate properties at execution time - in this instance to do dynamic table routing. The ‘onPrepareStatement’ is obviously called when Hibernate is preparing the sql statements just before it sends it to the DB. What the code is doing (if it isn’t obvious) is a simple string replacement of an exact string. This exact string is what we hard-code (can be anything but should be an identifier which will not occur otherwise in sql statements) and is to be replaced by the dynamic catalog and schema name. Where the actual catalog and schema name values come from is up-to the implementer. In the case of a web-application, I code this in the context as a String property and look it up via JNDI similiar to the DS.
I have deliberately made the above code very simple to show the underlying concept. One could put some quite complicated routing logic in there to do other forms of dynamic routing (e.g. based on logged in customer, based on users region). There are other solutions to the dynamic routing problem which address slightly different use-cases - check Hibernate Shards and dynamic models.
I am not sure the interceptor was meant for what I am ‘hijacking’ it to do but the first line of the api does give some insight…
Allows user code to inspect and/or change property values.
Opinions expressed by DZone contributors are their own.
Comments