Dynamic DataSource Routing |
|

Spring 2.0.1 introduced an AbstractRoutingDataSource. I believe that it deserves attention, since (based on frequent questions from clients) I have a hunch that there are quite a few 'home-grown' solutions to this problem floating around. That combined with the fact that it is trivial to implement yet easy to overlook, and now I have several reasons to dust off my corner of the Interface21 team blog.
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. One potential use-case is for ensuring transaction-specific isolation levels which are not supported by standard JTA. For that, Spring provides an implementation: IsolationLevelDataSourceRouter. Consult its JavaDoc for a detailed description including configuration examples. Another interesting use-case is determination of the DataSource based on some attribute of the current user's context. What follows is a rather contrived example to demonstrate this idea.
First, I created a Catalog that extends Spring 2.0's SimpleJdbcDaoSupport. That base class only requires an instance of any implementation of javax.sql.DataSource, and then it creates a SimpleJdbcTemplate for you. Since it extends JdbcDaoSupport, the JdbcTemplate is also available. However, the "simple" version provides many nice Java 5 conveniences. You can read more detail about that in this blog by Ben Hale.
Anyways, here's the code for my Catalog:
package blog.datasource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport;
public class Catalog extends SimpleJdbcDaoSupport {
public List<Item> getItems() {
String query = "select name, price from item";
return getSimpleJdbcTemplate().query(query, new ParameterizedRowMapper<Item>() {
public Item mapRow(ResultSet rs, int row) throws SQLException {
String name = rs.getString(1);
double price = rs.getDouble(2);
return new Item(name, price);
}
});
}
}
As you can see, the Catalog simply returns a list of Item objects. The Item just contains name and price properties:
package blog.datasource;
public class Item {
private String name;
private double price;
public Item(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public String toString() {
return name + " (" + price + ")";
}
}
Now, in order to demonstrate multiple DataSources, I created an enum for different Customer types (representing "levels" of membership I guess), and I created three different databases – so that each type of customer would get a distinct item list (I did mention that this would be a contrived example didn't I?). The important thing is that each of the databases are equivalent in terms of the schema. That way the Catalog's query will work against any of them – just returning different results. In this case, it's just the "item" table with 2 columns: name and price. And… here is the enum:
public enum CustomerType {
BRONZE,
SILVER,
GOLD
}
It's time to create some bean definitions. Since I have 3 datasources where everything is the same except for the port number, I created a parent bean so that the shared properties can be inherited. Then, I added the 3 bean definitions to represent the per-CustomerType DataSources:
<bean id="parentDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
abstract="true">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="username" value="sa"/>
</bean>
<bean id="goldDataSource" parent="parentDataSource">
<property name="url" value="jdbc:hsqldb:hsql://localhost:${db.port.gold}/blog"/>
</bean>
<bean id="silverDataSource" parent="parentDataSource">
<property name="url" value="jdbc:hsqldb:hsql://localhost:${db.port.silver}/blog"/>
</bean>
<bean id="bronzeDataSource" parent="parentDataSource">
<property name="url" value="jdbc:hsqldb:hsql://localhost:${db.port.bronze}/blog"/>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:/blog/datasource/db.properties"/>
</bean>
Notice that I added a PropertyPlaceholderConfigurer so that I could externalize the port numbers in a "db.properties" file, like so:
db.port.gold=9001 db.port.silver=9002 db.port.bronze=9003
Now things start to get interesting. I need to supply the "routing" DataSource to my Catalog so that it can dynamically get connections from the 3 different databases at runtime based on the current customer's type. As I mentioned, the AbstractRoutingDataSource can be rather simple to implement. Here is my implementation:
package blog.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
…and the CustomerContextHolder simply provides access to a thread-bound CustomerType. In reality, the 'context' would likely hold more information about the customer. Also note that if you are using Acegi, then you could retrieve some information from the userDetails. For this example, it's just the customer "type":
public class CustomerContextHolder {
private static final ThreadLocal<CustomerType> contextHolder =
new ThreadLocal<CustomerType>();
public static void setCustomerType(CustomerType customerType) {
Assert.notNull(customerType, "customerType cannot be null");
contextHolder.set(customerType);
}
public static CustomerType getCustomerType() {
return (CustomerType) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
Finally, I just need to configure the catalog and routing DataSource beans. As you can see, the "real" DataSource references are provided in a Map. If you provide Strings, they can be resolved as JNDI names (or any custom resolution strategy can be provided – see the JavaDoc). Also, I've simply set the 'bronzeDataSource' as the default:
<bean id="catalog" class="blog.datasource.Catalog">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="blog.datasource.CustomerRoutingDataSource">
<property name="targetDataSources">
<map key-type="blog.datasource.CustomerType">
<entry key="GOLD" value-ref="goldDataSource"/>
<entry key="SILVER" value-ref="silverDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="bronzeDataSource"/>
</bean>
Of course I'd like to see this working, so I've created a simple test (extending one of Spring's integration test support classes). I added 3 items to the "gold" database, 2 items to the "silver" database, and only 1 item to the "bronze" database. This is the test:
public class CatalogTests extends AbstractDependencyInjectionSpringContextTests {
private Catalog catalog;
public void setCatalog(Catalog catalog) {
this.catalog = catalog;
}
public void testDataSourceRouting() {
CustomerContextHolder.setCustomerType(CustomerType.GOLD);
List<Item> goldItems = catalog.getItems();
assertEquals(3, goldItems.size());
System.out.println("gold items: " + goldItems);
CustomerContextHolder.setCustomerType(CustomerType.SILVER);
List<Item> silverItems = catalog.getItems();
assertEquals(2, silverItems.size());
System.out.println("silver items: " + silverItems);
CustomerContextHolder.clearCustomerType();
List<Item> bronzeItems = catalog.getItems();
assertEquals(1, bronzeItems.size());
System.out.println("bronze items: " + bronzeItems);
}
protected String[] getConfigLocations() {
return new String[] {"/blog/datasource/beans.xml"};
}
}
…and rather than simply taking a screenshot of the green bar, you'll notice I've provided some console output – the results!:
gold items: [gold item #1 (250.0), gold item #2 (325.45), gold item #3 (55.6)] silver items: [silver item #1 (25.0), silver item #2 (15.3)] bronze items: [bronze item #1 (23.75)]
As you can see, the configuration is simple. Better still, the data-access code is not concerned with looking up different DataSources. For more information, consult the JavaDoc for AbstractRoutingDataSource.
Similar Posts
- SpringSource dm Server Admin Console
- Beyond the FactoryBean
- Adding an Atom view to an application using Spring's REST support
- Spring Java Configuration – What's New in M3
- XPath Support in Spring Web Services





Alef Arendsen says:
Added on January 25th, 2007 at 1:04 pmHey Mark,
the green bar would have been even better. We all speak the universal language of the green bar, don't we
.
Anyhow, good article. This will be useful for many people!
Paul says:
Added on January 26th, 2007 at 10:13 amThis is great, but what about enterprise connectors? Can we see an example how to retrieve different javax.resource.cci.Connection(Factory) instances by a key?
David Kilzer says:
Added on January 26th, 2007 at 1:16 pmNeat feature and great blog entry!
Is there a reason why you didn't define a "BRONZE" map entry in the CustomerRoutingDataSource? It seems to me that if someone uses the code later and doesn't assume that BRONZE is the default, they will cause an exception (or use the wrong datasource) by doing:
CustomerContextHolder.setCustomerType(CustomerType.BRONZE);
List bronzeItems = catalog.getItems();
assertEquals(1, bronzeItems.size());
System.out.println("bronze items: " bronzeItems);
Dave
Mark Fisher (blog author) says:
Added on January 26th, 2007 at 3:14 pmDave,
Thanks for pointing that out. Since the 'bronzeDataSource' is the default, it will use the correct database for the example you provided. In fact, it would use 'bronzeDataSource' for any un-mapped CustomerType (or null as in the example in the blog). That said, it would have probably been clearer if I had mapped BRONZE and then created a fourth dataSource called "guestDataSource". The intent was to demonstrate the usage of a default.
Robert Varga says:
Added on January 27th, 2007 at 4:51 amActually, I have another usage pattern in mind, but I am not sure it is entirely correct:
We have a system, in which in certain use cases we use a single Oracle non-XA datasource, and in other use cases we use an Oracle datasource going to the same schema but with XA driver, and another datasource together with XA.
However, the DAOs accessing the Oracle datasource should probably work in both kinds of situation.
Now, if I made two separate DAO beans, one wired up with the XA datasource and the other with the non-XA datasource, the service beans would have to contain both DAO beans, and it would have to be distinguished case by case, whether a certain service method should use this or that DAO bean. And it is actually not the responsibility of the service bean to know, if it will be called together with another service bean which goes to a different datasource, but the use case calling both.
Therefore it would be useful if it could be found out in runtime, whether the XA or the non-XA Oracle datasource should be used, and the DAO beans would not need to be duplicated.
So the AbstractRoutingDataSource could be extended to find out, if there is a current XA transaction in place, in which case the XA datasource is used, or not, in which case the non-XA datasource could be used.
The question is, are there any hidden problems with this idea?
Best regards,
Robert
John Brinnand says:
Added on January 27th, 2007 at 12:40 pmGreat entry – it will be very useful for us. Since our db schema goes through changes, we are faced with the perennial problem of migrating data from one schema to another.
To solve this problem we developed a db migration tool, but it naturally, it requires constant re-configuration to point to our various datasources. Of course there are many ways to solve this, but after reading this entry, I suspect that using dynamic datasource routing is the best solution so far.
Thanks!
Thanks!
Ganeshji Marwaha says:
Added on January 29th, 2007 at 4:40 amVery interesting blog.
This will be very useful in an ASP model where each customer is given a separate database. Using this model, the data-source for individual customers can be switched transparently. All they will need to do is to extend AbstractRoutingDataSource that gets the logged-in user's corresponding data-source.
Thanks. Enlighten us with more like this.
–>Ganesh.
Frank Groot says:
Added on January 29th, 2007 at 7:54 amMark,
Thank you. Just what we needed to solve our problem….
Regards,
Frank
Stefan Fleiter says:
Added on January 29th, 2007 at 8:11 amI've written very similar code to solve this problem some time ago. A missing part to a solution would be an AbstractRoutingSessionFactory for Hibernate. Is something like this planed or are you interested in a code contribution for this?
Robert Varga says:
Added on January 29th, 2007 at 9:52 amStefan,
Why would you need that? I expect, you just need to specify the routing data source to the session factory.
BR,
Robert
Robert Varga says:
Added on January 29th, 2007 at 9:54 am[quote comment="9759"]Stefan,
Why would you need that? I expect, you just need to specify the routing data source to the session factory.
BR,
Robert[/quote]
I mean to the LocalSessionFactoryBean.
BR,
Robert
Stefan Fleiter says:
Added on January 29th, 2007 at 11:56 am[quote comment="9760"]
Why would you need that? I expect, you just need to specify the routing data source to the LocalSessionFactoryBean.
[/quote]
Robert,
many thanks for asking the question, I did forget to mention this.
You need different session factories for hibernate if the database instance is part of the primary key. In this case having a single session factory would defeat hibernates' view of object identity and would deliver wrong results if you activate second level caching.
Robert Varga says:
Added on January 29th, 2007 at 12:21 pmHi Stefan,
yes, that's right.
However, could you not divide the pk space between the different sessions?
BR,
Robert
kathir says:
Added on January 29th, 2007 at 6:51 pmVery good article. We were struck with the static data source in our code. Now this solves our problem..
Moshe says:
Added on January 30th, 2007 at 2:56 amHi there,
I was also looking for a solution for this kind of problem and the blog entry helps a lot, so thanks
Unfortunately, as Stefan noted, when working with Hibernate, the solution of just supplying a routing data source to the LocalSessionFactoryBean is problematic, especially because of possible 2nd level cache collisions. In my specific scenario, we have perhaps dozens of different DBs and we can't afford to have a session factory for each one due to the high memory consumption of this object. It seems like we're going to have to reuse the same session factory (obviously assuming all DBs share the same tables structure).
I started thinking about having different cache regions: one region per cached class/query and per DB.
If anyone can help with a better idea, or wants to shout at me "STOP what you're trying to do, don't go there since it is very dangerous" – all comments are welcome
Thanks,
Moshe
Dinesh says:
Added on January 30th, 2007 at 11:32 amMark, Thanks for the blog…we are using the same approach to support multiple data sources.
we also want to support multiple schema for hibernate session factory implementation.
Is there anything new in Spring 2.0.1 ?
Thank you,
Dinesh
John Lindwall says:
Added on February 2nd, 2007 at 4:09 amExcellent post! Well written and fun to follow along. Was the ThreadLocal bit thrown in just for fun? There is no *requirement* to use a ThreadLocal for the discriminator, right? I've alway been steered away from using ThreadLocal — isit a common solution?
We too were hoping to use this technique with Hibernate but apparently this is problematic. Too bad.
Robert Varga says:
Added on February 2nd, 2007 at 8:01 amHi John,
ThreadLocal is not strictly required, but I don't really see another way of passing occurence-related information to the AbstractRoutingDataSource.getConnection() method in a thread-safe manner which does not involve ThreadLocal behind the scenes.
A ThreadLocal is a perfectly correct tool to use, only you have to ensure that you clean it up, once you are done with it, otherwise you can leak memory and classloaders.
Best regards,
Robert
Srini says:
Added on February 20th, 2007 at 4:39 pmHi Mark,
Your post is very informative. I have a single database with multiple schemas, where users each have their own separate sets of tables through schema.
Database-> DB Schema
GoldDB -> Project1_Schema, Project2_Schema etc
Each Schema will have the same set of DB Tables. i.e
Project1_Schema -> Catalog, Product, Item
Project2_Schema -> Catalog, Product, Item
Public_Schema -> Users
Some users will have access to Product_Schema1. Some will have access to Product_Schema2. How can I create a dynamic data source connection for the webapp.
Thanks for your help.
Regards,
Srini.
ivan says:
Added on March 3rd, 2007 at 4:36 amedwedwedw
G.Rajesh says:
Added on March 21st, 2007 at 7:02 amwhat do i do if i don't know the number of datasources. Not like the three customer types.I need to create the datasources during run time.
Ken DeLong says:
Added on April 5th, 2007 at 3:25 pmI'm trying to understand if this is irretrievably broken for Hibernate. If I have multiple partitioned databases, with the exact same schema in each one, and if the id's of the objects are kept unique across all the databases (a good idea if you ever plan on migrating data from one partition to another) would this solution not work? I'm not understanding the second-level cache collision; if the ids of the objects are unique wouldn't this be ok?
Mark Fisher (blog author) says:
Added on April 5th, 2007 at 3:35 pmKen, it is true that you could avoid collisions through globally-unique identifiers. However, it seems that the ideal solution would involve dynamically routing to different SessionFactory instances. I plan to post a follow-up that addresses that within the next few days.
Ken DeLong says:
Added on April 5th, 2007 at 4:35 pmMark,
Thanks for the quick answer! I've just now gotten the AbstractRoutingDataSource to work with Hibernate in our partitioned database scheme. I've also tested out the solution that you proposed above: a different SessionFactory for each database, with duplicate service configs (dao1, dao2, etc) and a "routing" interceptor that wraps the service and picks the correct dao to route to.
This was my first design choice, but there are several problems: many SessionFactories is expensive in terms of resources and startup time. It's also a configuration mess, with bunches of SessionFactories, dao defns, and txn managers running around. (We're not [yet] using JTA; maybe we should). Also, I've implemented my routing interceptor with @AspectJ style (which is *very* nice), but I cannot force it to allow the txn interceptor to bind *first* – it insists on wrapping the dao directly, which means that the Session/txn starts up *before* I can select a datasource, and that's bad.
Overall, the RoutingDatasource seems like a winner to me. What makes you think that the "multiple SessionFactory" style is superior? Perhaps I'm missing something crucial…
paul says:
Added on April 17th, 2007 at 11:30 amNice article. I'm new to Spring but that made sense, unlike so many other things I am reading. I am trying to do something similar except that my databases are defined in a table and fetched when the user logs in: the user picks one from the list (or defines a brand new one).
Do you think your approach would work for this, or do the datasource deftns really need to be known at initialisation time? I've been reading about HotSwappableTargets and Proxies but don't understand that yet.
burt says:
Added on April 23rd, 2007 at 10:14 amHey I ran into some problems using this method. I have 3 datasources, devDS, QADS, prodDS. My app points to devDS by default. Now using this method i was able to change DS's on the fly. However, I recognized a problem. I would switch to a DS say QADS, and then navigate around and realize that i am back at the default devDS. I can't seem to figure out how to fix this. I think what is happening is that a new thread is being started that uses automatically uses the default DS. How would i go about fixing this?
-B
Mark Fisher (blog author) says:
Added on April 23rd, 2007 at 11:20 amThat sounds like you are losing the thread-bound key info. Depending on your situation, you may be able to resolve it by using an InheritableThreadLocal?
burt says:
Added on April 23rd, 2007 at 12:04 pmnot sure how i would do that…I am a newbie to spring, Can you elabourate on this for me please. this would be appreciated. Thank you
-B
eric says:
Added on April 24th, 2007 at 12:33 pmWe want to use a single SessionFactory for multiple schemas without comprimising the second-level cache. I read Moshe's post on setting up a separate cache region per cached class/query and per DB.
We want to do similar thing and we're thinking about adding the schema's name to the cache region name. Does anyone have any idea of how to achieve this?
Any idea is appreciated.
-E
Venkat says:
Added on May 4th, 2007 at 12:02 pmFor those trying to acheive this with Hibernate, Try Hibernate Shards.
Oskar says:
Added on May 11th, 2007 at 4:50 amHello,
I have the same challenge as Paul, but I don't really know how to do it. Basically, I will have a table with a relationship between usernames and jdbc urls. How can I tell at execution time what connection should be used? that can be done using AbstractRoutingDataSource?
I spend a lot of days trying to figure out how to do this, and your example is the closest thing I found, but I don't know how to go on.
I really appreciate any help you could bring to me.
Thank you for your time,
Oskar
Oskar says:
Added on May 11th, 2007 at 4:54 amHello,
I have the same challenge as Paul. I need to decide at execution time which connection should I use, and it must be populated from one table. Basically, I have username ranges and jdbc urls stored, so depending on the username who logins into the application, I should use a different connection.
I have been researching for a lot of days, and the AbstractRoutingDataSource is the closest thing I found. But I don't know how to afford this.
I will really appreciate any help which can point me on the right direction…
Thank you very much for your time,
Oskar
Oskar says:
Added on May 14th, 2007 at 7:53 pmHello again,
I'm really getting desperated with this… please, can someone tell me how to approach this?
Thank you!,
Oskar
Oskar says:
Added on May 14th, 2007 at 9:32 pmOh sorry, I put the same comment twice and I don't know how to delete. Sorry for that, I was not intentionally…
trung says:
Added on May 22nd, 2007 at 8:00 amMark
Can we switch several datasources within a thread (context)? How about the transaction manager in this case?
Thanks
kasim says:
Added on May 23rd, 2007 at 9:23 amHi,
I have 2 issues now:
1) One database two or more scheema i need to use.
2) two or more database with two or more scheema i need to use.
how can i solve this? i am using Spring jdbc template.
Vio says:
Added on June 13th, 2007 at 5:42 pmHello,
I'm new to Spring and trying to use AbstractRoutingDataSource.
I'm getting the following error when application context starts to initialize:
(Error) Attribute 'key-type' used but not declared.
I have to use a specific data source based on the year and I have something like this:
……………………………………..
………………………….
Am I missing something?
Thanks,
Vio
Vio says:
Added on June 13th, 2007 at 5:44 pmHello,
I'm new to Spring and trying to use AbstractRoutingDataSource.
I'm getting the following error when application context starts to initialize:
(Error) Attribute 'key-type' used but not declared.
I have to use a specific data source based on the year and I have something like this:
Am i Missing something?
Thanks,
Vio
Sverre Moe says:
Added on June 20th, 2007 at 7:43 am[quote comment="17569"]Ken, it is true that you could avoid collisions through globally-unique identifiers. However,
it seems that the ideal solution would involve dynamically routing to different SessionFactory instances. I plan to post a follow-up that addresses that within the next few days.[/quote]
Did you post a follow up?
Sverre Moe says:
Added on June 20th, 2007 at 8:02 am[quote comment="17575"]Mark,
I've just now gotten the AbstractRoutingDataSource to work….
Overall, the RoutingDatasource seems like a winner to me. [/quote]
How did you configure it? I have a LocalSessionFactoryBean which reference the routing datasource. I am unsure if it will work.
Sverre Moe says:
Added on June 21st, 2007 at 6:40 amI guess this articles comments are dead. No activity for a while
Well I'll past my last question.
After implementing the AbstractRoutingDataSource I across a problem which was not in the article. I have 8 databases which my application connects to determined by the users choice. It seems like I get a "Failed to look up JNDI DataSource with name myDataSource1". This problem I solves by creating a JNDI named myDataSource01 in WebSphere(My J2EE container) for database 1. The other datasources did not need a JNDI name.
Why is this?
Berre says:
Added on June 21st, 2007 at 8:20 amLoved reading about all of this
I'm also trying to do something like this, but my requirement is not so specific as the crazy stuff you guys have to do!
Enjoy The Java!
Mark Fisher (blog author) says:
Added on June 21st, 2007 at 10:39 am[quote comment="28694"]It seems like I get a "Failed to look up JNDI DataSource with name myDataSource1". This problem I solves by creating a JNDI named myDataSource01 in WebSphere(My J2EE container) for database 1. The other datasources did not need a JNDI name. Why is this?[/quote]
Sverre,
The "targetDataSources" map can contain values that are actual DataSource references or Strings. In the latter case, the String will (by default) be treated as a JNDI-name.
Hope that helps,
Mark
Ian Long says:
Added on July 24th, 2007 at 3:19 pmI have been using this idea in my webapp successfully except for 1 case. I have a filter that I use to set the context holder to a value so my lookup is successful. Normally, this works nicely, except I've run across a problem using jasperreports. Basically, jasperreports is spawning a thread which ends up using my datasource to grab a connection, and the context hasn't been set, so it uses the default datasource, which in most cases isn't correct. Can anyone think of a good way to handle scenarious like this, where I can't call the equivalent of setCustomerType(), because I don't have access to a spawned thread?
Any help is appreciated!
Mark Fisher (blog author) says:
Added on July 24th, 2007 at 3:34 pm[quote comment="36082"]Can anyone think of a good way to handle scenarious like this, where I can't call the equivalent of setCustomerType(), because I don't have access to a spawned thread?[/quote]
Would it be possible to store the customer type in an InheritableThreadLocal?
Ian Long says:
Added on July 24th, 2007 at 5:21 pm[quote comment="36083"][quote comment="36082"]Can anyone think of a good way to handle scenarious like this, where I can't call the equivalent of setCustomerType(), because I don't have access to a spawned thread?[/quote]
Would it be possible to store the customer type in an InheritableThreadLocal?[/quote]
I'm not familiar with InheritableThreadLocal, I'll have to do some research…thanks for the suggestion.
Ian Long says:
Added on July 24th, 2007 at 5:32 pm[quote comment="36083"][quote comment="36082"]Can anyone think of a good way to handle scenarious like this, where I can't call the equivalent of setCustomerType(), because I don't have access to a spawned thread?[/quote]
Would it be possible to store the customer type in an InheritableThreadLocal?[/quote]
Thanks Mark! Using an InheritableThreadLocal does indeed solve the problem – it works like a charm.
Rajesh says:
Added on November 13th, 2007 at 6:10 pmI am getting the "key-type" error in spring configuration xml file.
here is the error :
Attribute "key-type" must be declared for element type "map".
am I missing something.
Thanks,
Rajesh
Alberto Flores says:
Added on December 6th, 2007 at 4:15 pm[quote comment="17569"]Ken, it is true that you could avoid collisions through globally-unique identifiers. However,
it seems that the ideal solution would involve dynamically routing to different SessionFactory instances. I plan to post a follow-up that addresses that within the next few days.[/quote]
Are there any plans on responding on this? I'm taking a look (as suggested by Venkat) to look into Hibernate Shards, but will be interested to see an approach using Spring.
Alberto Flores says:
Added on December 6th, 2007 at 4:18 pm[quote comment="17569"]Ken, it is true that you could avoid collisions through globally-unique identifiers. However, it seems that the ideal solution would involve dynamically routing to different SessionFactory instances. I plan to post a follow-up that addresses that within the next few days.[/quote]
Neil,
Excellent discussion, however I'm wondering if there were any plans on following up (as quoted) on using different SessionFactory instances (although such approach would potentially consume a lot of resources). I'm currently looking into Hibernate Shards (as suggested by Venkat), but will be very interested in learning of an approach using Spring.
Thanks,
Alberto
Chuck Canning says:
Added on January 3rd, 2008 at 5:50 pmDid you ever post the Hibernate version. Also, would this approach work with annotation driven transactions?
mike wu says:
Added on January 15th, 2008 at 5:09 amspring is too complex now
aapddeevv says:
Added on March 2nd, 2008 at 6:29 pmI saw some notes about creating data sources programmatically in your application (such as when the data sources are user configured) and still be able to create your session factories, which are dependent on the data source, in the proper way. I blogged on this awhile ago. Another way to handle this is described here using hierarchical contexts. The key thought is that by using parent context's you can control the creation of a group of beans as a creation unit. The data source layer is a parent layer to the session factories and you merely create the data source context first, configure it, then create a context with the session factory with the data source context as a parent: blog location.
I'll give the dynamic datasource routing approach a try as well.
Craig says:
Added on March 18th, 2008 at 8:31 pmThis is a useful article…
http://affy.blogspot.com/2007/11/dynamic-datasource-via-spring-using.html
Lior says:
Added on April 7th, 2008 at 7:54 amHi, great article.
I've posted a question regarding this method on the board:
http://forum.springframework.org/showthread.php?t=52224
would be happy if anyone can comment
thanks.
cemil says:
Added on May 6th, 2008 at 5:30 amHi,
Thanks for the article.
I implemented some functionality that use HotSwappableTargetSource to switch from multiple datasources . The problem is when user1 goes into application he will use dataSource1 for finding some data .When user2 goes into application he will use dataSource2 to find some data , and I have only swappable target datasource , then when the user1 will try to made a new search he will find the datasource swap to datasource2 which is not good .
How can I bound the datasource used from the user session?
Or how can i prevent this from occuring by maybe thread binding the datasource switching ?
Any suggestions ?
From Cem
Cemil says:
Added on May 7th, 2008 at 5:19 amYou have this comment on youre blog:
"In reality, the 'context' would likely hold more information about the customer. Also note that if you are using Acegi, then you could retrieve some information from the userDetails."
Can you show me how to do this…a small example or something.
Best regards
Rajender Aggarwal says:
Added on September 22nd, 2008 at 5:09 amHi,
Please refer to the link http://forum.springframework.org/showthread.php?t=52224. Even I am facing the same issue. Thoughts/Suggestions? Any explanation for the resolution suggested?
Krishna says:
Added on October 1st, 2008 at 7:38 amHi Mark,
I have a doubt in using multiple databases depends on the users preferences, In my application the user is allowed to change database runtime. first the default database is to be loaded after user login there is a drop down list which shows different databases from which the user can choose one. my applicationContext.xml
${db.driverClassName}
${db.1.url}
${db.1.username}
${db.1.password}
I need to use a database at a time
All I need is to change the url only all other property are same …
I try few methods to change the db but all in vain
thanks in advance
Murray says:
Added on October 3rd, 2008 at 12:20 pmHi Mark,
I realize this is an old post, but just wondering if there's been any update on what the current best practices are for using spring and hibernate with multiple databases? Configure a session factory for every routing data source? Dynamically route to different SessionFactory instances based on a user context?
A common use case seems to be 1 database per tenant with identical schemas (Saas). From what I can tell, Spring Hibernate still don't scale this use case easily without quirky workarounds. Can anyone shed some light?
M
Krish says:
Added on October 4th, 2008 at 4:57 amHi Mark,
I try to use your sample to switch databases but its loading the same database,
my applicationContext.xml
this is my java class which sets the new database
package com.test.view.bean;
import org.springframework.util.Assert;
public class ChangeContextHolder {
public enum DataBaseType {
DEMO1,
DEMO2,
DEMO3,
}
private static final ThreadLocal contextHolder =
new ThreadLocal();
public static void setDataBaseType(DataBaseType dataBaseType) {
Assert.notNull(dataBaseType, "DataBaseType cannot be null");
contextHolder.set(dataBaseType);
}
public static DataBaseType getDataBaseType() {
return (DataBaseType)contextHolder.get();
}
public static void clearDataBase() {
contextHolder.remove();
}
}
this is my class extending the AbstractRoutingDataSource
package com.ncsts.view.bean;
import java.util.Map;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDataSource extends AbstractRoutingDataSource{
private Map targetDataSources;
private Object defaultTargetDataSource;
@Override
protected Object determineCurrentLookupKey() {
return ChangeContextHolder.getDataBaseType();
}
}
and I set the new database from my bean class, every thing seems to be ok but its not switching the databaase its loading the same database first loaded.
thanks in advance
simpledong says:
Added on October 9th, 2008 at 10:39 pmHi Mark,
I'm a reader from China
Your article is so useful for me
Thank you very much:)
Pankaj says:
Added on November 7th, 2008 at 7:15 amHi Mark,
You code (blogged) seems promising, but I tried in my case of associating AbstractRoutingDataSource with LocalSessionFactoryBean, but still it's not swapping..
Here is a glimpse of what I have done..
//This class extends AbstractRoutingDataSource
//Gives the javax.sql.DataSource references
public class EtsAuthDataSourceSwapper extends AbstractRoutingDataSource {
@Override
public Object determineCurrentLookupKey () {
return DataSourceContextHolder.getDataSourceName();
}
}
public class DataSourceContextHolder {
private static final ThreadLocal dsNameHolder = new ThreadLocal ();
public static void setDataSourceName(DataSourceName dsName) {
Assert.notNull(dsName, "DataSourceName cannot be null");
dsNameHolder.set(dsName);
}
public static DataSourceName getDataSourceName() {
return dsNameHolder.get();
}
public static void removeDataSourceName() {
dsNameHolder.remove();
}
}
//Utility method to swap database/db at runtime (currently hardcoded)
public class GenericBO {
public void swapDataSource(String datasourceName) {
System.out.println("Calling new DS, dude..Moment of truth..");
DataSourceContextHolder.setDataSourceName(DataSourceName.ETSAUTHPRODGLOBAL);
}
}
public enum DataSourceName { ETSAUTHQAGLOBAL, ETSAUTHPRODGLOBAL;
}
Its almost on similar lines, of what you have suggested except that I have not used DriverManagerDataSource but rather have my own factory method to gimme DataSource objects.
Please suggest what am I doing wrong, as I might be very near to the destination, but still could't realise the gap..
Pankaj says:
Added on November 7th, 2008 at 7:19 amSeems it ate my config entries..
Here are they..(in continuation to above)
Pankaj says:
Added on November 7th, 2008 at 7:24 amIt ate my entries again..seems it doesnt accept xml data..trying last time.. Apologies!
leosun says:
Added on December 30th, 2008 at 2:12 amI'm reader from china.
The article is very useful for me.
I have some questions to reloved.
If my project use hibernate,but how to config the sessionfactory,or
if i change to ibaties in my project,how to config the sqlmapclient?
please help me.
thank you.
slobodanka says:
Added on January 7th, 2009 at 7:36 amHello Mark, Can you help me with the following problem:
I have two datasources configured:
and I want to switch two from asapLoggingDataSourcePrimary to asapLoggingDataSourceSecondary if asapLoggingDataSourcePrimary is not available. I thought somthing like this but I didn't get it working:
I am a new Spring user. Please help me.
slobodanka says:
Added on January 7th, 2009 at 9:07 amPlease can you help me with the following problem. I have two data sources: asapLoggingDataSourcePrimary and asapLoggingDataSourceSecondary.
If connection with asapLoggingDataSourcePrimary is broken (org.springframework.dao.DataAccessException is trown) I want dynamic routing to secondary data source. Something like:
Wayne Earnshaw says:
Added on March 10th, 2009 at 3:31 pmI guess the only problem with this approach using DriverManagerDataSource is as the Spring API doc states "…NOTE: This class is not an actual connection pool; it does not actually pool Connections. It just serves as simple replacement for a full-blown connection pool, implementing the same standard interface, but creating new Connections on every call… Useful for test or standalone environments outside of a J2EE container… ". Since I'm currently using Apache commons DBCP I wouldn't want to lose the connection pool in a production application.
Mark Fisher (blog author) says:
Added on March 10th, 2009 at 4:11 pm@Wayne
You can use the DBCP DataSource. The DriverManagerDataSource was simply used here as an example.
Wayne Earnshaw says:
Added on March 12th, 2009 at 2:53 pmThanks Mark for the excellent article,
I've implemented this now and as you say it works well with the Apache DBCP DataSource instead of DriverManagerDataSource too (Spring is nice and flexible). Next for me is to decide if I want to Hibernate this part or if I'll just stick with Spring…
Ed Murphy says:
Added on April 4th, 2009 at 1:42 pmMark,
Thanks for this excellent article. I believe it solves an issue I will have an an upcoming project. In that project, I will spin up new customers by creating identical schemas with different catalog names (based on the customer). When a customer using logs it, I need to be able to route them to their version of the catalog. I think AbstractRoutingDataSource will work for this. However, I would like to use JPA annotations. I know I can use Hibernate and intercept the SQL query and replace the catalog name in the query. However, I'd like to use this with EJB3 and no hibernate. Can I use JPA annotations with AbstractRoutingDataSource to achieve my goals? Thanks again for the valuable article.
Ed Murphy
Nethaji says:
Added on April 28th, 2009 at 2:00 amHi Mark,
I have 2 issues now:
1) One database two or more scheema i need to use.
2) two or more database with two or more scheema i need to use.
how can i solve this? i am totally new to Hibernate And Spring.
NapiArgo says:
Added on June 7th, 2009 at 3:00 amHi Mark,
Thax alot, this solve my problem
HeeGu says:
Added on August 3rd, 2009 at 12:43 pmHi, This is good article. Simple solution than JTA.
But this solution must be uses very carefully in multi-thread context. In SomeBOImpl, a method is exist to below.
public void printAllItems() {
printGoldItems();
printSilverItems();
printBronzeItems();
}
public void printGoldItems() {
CustomerContextHolder.setCustomerType(CustomerType.GOLD);
List goldItems = catalog.getItems();
assertEquals(3, goldItems.size());
System.out.println("gold items: " goldItems);
}
public void printSilverItems() {
CustomerContextHolder.setCustomerType(CustomerType.SILVER);
List silverItems = catalog.getItems();
assertEquals(2, silverItems.size());
System.out.println("silver items: " silverItems);
}
public void printBronzeItems() {
CustomerContextHolder.clearCustomerType();
List bronzeItems = catalog.getItems();
assertEquals(1, bronzeItems.size());
System.out.println("bronze items: " bronzeItems);
}
And two anonymous threads is runned printAllItems() after get SomeBOImpl in spring-context using BeanFactory at almost same time.
Is catalog.getItems() method run correctly as our wish?
Who is ThreadLocal's key? SomeBOImpl? or anonymous thread?
Manish says:
Added on August 12th, 2009 at 3:43 pmI tried implementing this , worked like a charm…Great Blog Mark !!
Ondrej Burkert says:
Added on September 22nd, 2009 at 6:45 amHi,
I tried this approach and it works just fine but as we use Hibernate, the second level hibernate cache did not work. One of the possible solutions to this is to create separate session factory for each data source. But as the session factory object is quite resource demanding and the schema of the data sources were every time the same I tried to find a way around it. In the end we ended up by wrapping CacheProvider and adding our wrapper for the Cache to the system.
The main idea is to prefix cache entries by the data source identificators.
In steps:
1. Implement your AbstractRoutingDataSource and supply it to your Hibernate session factory as described in the blog entry.
2. Implement a wrapper to the CacheProvider which will contain field with CacheProvider implementation supplied from the Spring configuration for instance.
3. The methods of your CacheProvider are just delegating work to the underlying cache provider except for the buildCache method which creates the CacheWrappers:
public Cache buildCache(String name, Properties properties) throws CacheException {
Cache cache = cacheProvider.buildCache(name, properties);
if (!(cache instanceof OKbaseCache)) {
cache = new CacheWrapper(cache, dataSourceProvider);
}
return cache;
}
4. Implement CacheWrapper – it implements interface org.hibernate.cache.Cache and it prefixes the keys of elements which are put/retrieved from the cache by a context based information:
for instance:
public class CacheWrapper implements Cache {
private Cache underlyCache;
public CacheWrapper(Cache underlyCache) {
this.underlyCache = underlyCache;
}
@Override
public void clear() throws CacheException {
underlyCache.clear();
}
….
@Override
public Object get(Object key) throws CacheException {
return underlyCache.get(CustomerContextHolder.getCustomerType() key));
}
Every use of the "key" must be prefixed correspondingly. Of course it might be wise to define your own key which would work with the prefix (data source identification) and the "Object key" and would define correctly hashCode and equals…
In the end you have to inject your CacheProvider into the SessionFactory.
The Hibernate second level cache will work as everytime a user connects to a given data source, he consults the region of cache prefixed with the data source identification, thus objects with same ID from different data sources would not clash.
Note, from Hibernate 3.3 the use of Cache interface is deprecated and we should use RegionFactory instead. But as most caches still work on the base of Cache interface, I guess it can wait to rewrite it to the version using RegionFactory rather than CacheProvider…
yanz says:
Added on September 30th, 2009 at 4:48 amThank you for this great post. In my web applicaiton user related information is saved in session after user Logs In and i have to to decide datasource name based upon this information. How can i make httpsession available to custom data source resolver class. I'm using Spring and Tapestry.
Product data Entry says:
Added on December 3rd, 2009 at 4:05 amGreat post. The content here is really very informative.
Jithen says:
Added on January 20th, 2010 at 2:25 pmIts an amazing implementation to reduce app developers tasks. Thanks for publishing. I have a special requirement where I should load different hibernate mappings with each data source based on its key. Could anyone help me understand the approach? Regards.
Gold Profit says:
Added on May 29th, 2010 at 8:35 amThis is a good is a good site so i like it.
Gold Profit
John says:
Added on June 10th, 2010 at 10:37 amHi,
I have a problem when I try to run this example. anybody could help me thanks?
Exception in thread "main" org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 41 in XML document from class path resource [resources/applicationcontext.xml] is invalid; nested exception is org.xml.sax.SAXParseException: Attribute "key-type" must be declared for element type "map".
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:404)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:342)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:212)
at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:113)
at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:80)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:123)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:422)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:352)
at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:83)
suzen says:
Added on August 3rd, 2010 at 2:30 amEarn online easy money at your door step for about 10$ on just one So go ahead and click here click hurry up!(smart way to make fast money).
Thank you.
CRM India says:
Added on September 1st, 2010 at 2:19 amVery good post and good site. I appretiate your work.
stevetee says:
Added on November 3rd, 2010 at 4:44 pmExcellent article. Flexibility of Spring demonstrated yet again.
Srinath says:
Added on November 19th, 2010 at 1:05 amHi,
When I customized the above example to use in my application which has spring working with db2, the switching of datasource does not happen.
It always points to default datasource.
Can you please let me know, where might be the problem.
Note: I have only two data sources, one given in map and other is default. and even when i switch the database in my test class, its still referencing the default data source.
Thanks in advance.
Regards,
Srinath
Stephan Beutel says:
Added on February 8th, 2011 at 3:06 amHello,
very good article. The implementation works.
But with Hibernate I can't change the database.
Always the default database is used.
Is there an article/blog entry/howto like this one available which shows a
similar implementation which works with Hibernate?
Thanks a lot.
Regards,
Stephan
Guru says:
Added on February 8th, 2011 at 3:22 pmYou may need to configure session factories instead of data sources. Hope this works.
Gyanendra says:
Added on February 10th, 2011 at 5:28 amHi,
I am trying this example but not getting the exact flow…can any body guide more….
Thanks in advance…
—————
Gyana
Kyle Adams says:
Added on March 4th, 2011 at 4:28 pmThis example is working great for the most part. I have 3 data sources. The 1st and 3rd return a result set as expected every time, but the 2nd fails every time.
Here is my implementation code:
* Load an existing Po entity
*
*/
@Transactional
public Po getPoByPoNumber(String poNumber) {
// Get Po by Po number from the default CMC Reporting data source
Po po = poDAO.getPoByPoNumber(poNumber);
// if the Po is null, then get Po by Po number from CLS Reporting data source
// THIS NEVER RETURNS A RESULT SET?!??!?!
if(po == null){
DataSourceContextHolder.setTargetDataSource(DataSourceEnum.CLS);
po = poDAO.getPoByPoNumber(poNumber);
}
// if the Po is null, then get Po by Po number from MTL Reporting data source
// This returns a result set
if(po == null){
DataSourceContextHolder.setTargetDataSource(DataSourceEnum.MTL);
po = poDAO.getPoByPoNumber(poNumber);
}
// if the Po is STILL null, then instantiate Po
//and set the CreatedUsername to "No Matching PO"
if(po == null){
po = new Po();
po.setPoCreatedUsername("No Matching P0");
}
return po;
}
Here is my application context:
Ricardo says:
Added on April 14th, 2011 at 4:30 amIm getting a null pointer exception:
============================================
//DAO
public class TestDAO extends SimpleJdbcDaoSupport {
public List<Map> getDomains() {
String query = "select 1 from dual";
return getSimpleJdbcTemplate().queryForList(query);
}
}
============================================
//Routing
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return SessionContextHolder.getDBServer();
}
}
============================================
//controller
@Controller
@RequestMapping(value="/test")
public class HandleRequest {
@RequestMapping(method=RequestMethod.GET)
public String handle(Model model){
TestDAO testDAO = new TestDAO();
SessionContextHolder.setDBServer("server1");
List domains = testDAO.getDomains();
model.addAttribute("domains", domains);
return "test1";
}
}
============================================
============================================
And this is the error
java.lang.NullPointerException
springtest.TestDAO.getDomains(TestDAO.java:15)
Kyle says:
Added on April 14th, 2011 at 6:07 amMy issue has been resolved BTW. I just had to remove the transactional annotations.
Jose says:
Added on May 5th, 2011 at 1:11 pmHey I can access to a different databases, how can do it? thanks
Saravana says:
Added on July 13th, 2011 at 12:24 amHi, I am facing an issue. I have configured two data sources one is JNDI based which is used for production. Other is DriverManager based which is used for testing. I am using the second datasource to be used when running unit tests(without the app server). But both of my data sources are trying to get created, only that the usage of the data source is determined based on the type set in the context. I tried setting lazy-init="true" for Production Data source bean but no use.
prabuddha roy says:
Added on August 3rd, 2011 at 11:12 amHi Mark,
Please let us know if you figure out the Hibernate SessionFactory usage with your approach. You promised long time back as quoted below.
Hope you are still actively following your invention feedback.
[Mark Fisher (blog author) says:
Added on April 5th, 2007 at 3:35 pm
Ken, it is true that you could avoid collisions through globally-unique identifiers. However, it seems that the ideal solution would involve dynamically routing to different SessionFactory instances. I plan to post a follow-up that addresses that within the next few days.]
Aravindan says:
Added on September 13th, 2011 at 6:38 amHi,
Here in the above example i see the Database details i.e URL/Port/UserName/Password will be know and its configured in properties file for the different Database's that we need to use. But I have a simillar use case where the datadase name will be dynamic i.e we need to get the database name i.e "User_Product_Profile" by passing the username and product details when the user selects the product. Basically the mapping of database that we need to use for each username and product will be get from a different database i.e DB_Profile Table of Customer Database.
Please let me know how to configure such use case with spring.
Thanks,
Aravindan
Thangaraj says:
Added on December 29th, 2011 at 10:40 amHi,
The article is interesting!. But it seems hard coded datasources not dynamically. May be i have misunderstood. Anyway, how will you pass values to the place of database configuration.
Hope you reply soon!
Thangaraj says:
Added on December 29th, 2011 at 11:54 amforgot to ask… continued…
…how will you pass values to the place of database configuration from database dynamically.
Manoj Singh says:
Added on May 29th, 2012 at 5:06 amI read your blog. Very interested blog. Greeting from India.
Manoj Singh says:
Added on May 29th, 2012 at 5:08 amVery interested blog. Thanks for sharing!!
Rick says:
Added on June 21st, 2012 at 3:04 pmI'm trying to implement data source routing with a org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean. It throws an BeanCreationException saying that the entityManagerFactory cannot resolve reference to 'parentDataSource' because it is abatract. Is there a way to use an entityManager with an abstract dataSource?
Ido says:
Added on September 23rd, 2012 at 1:13 amHello,
I am using spring routing data source as explained here and things works well. Now, I want to add connection pooling (Apache DBCP). I read here that all I have to do is to change the basic data source to the connection pool data source. well, It does not work. On the server start-up I see that connection pooling is happening and I can debug Apache's code, but then, when I am trying to access the DB through my code, I go to the routing data source, and from there to the DriverManager class to get a connection – completely ignoring Apache's code.
Can you help me please?
Vedran says:
Added on September 28th, 2012 at 7:48 amIs it somehow possible to share a transaction between the datasources with this approach?
So far even though I select the other datasource, everything executes on the datasource transaction was started on.
Warner Piñero says:
Added on October 1st, 2012 at 11:31 pmThis post was since 2007. Is there a new way to handle multiple datasources/db engine used using Spring 3.x?