Skip to content

JPA: Dynamic Datasource Routing

2011 March 4
tags: , ,
by Haf

Man stelle sich vor, in einer (Web-)Anwendung müssen verschiedene Datasources verwendet werden. Ob bei den unterschiedlichen Datasourcen es sich immer um die andere/gleiche Datenbank handelt, sei mal dahingestellt. Die Datasourcen unterscheiden sich mind. in einer Eigenschaft, z.B. User.

Im Spring-Kontext gilt folgendes: In JPA braucht man pro EntityManagerFactory eine Datasource. Der EMF ist einen TransactionManager zugeordnet.

Man könnte alle Datasource, EMF, TX-Manager und PUs in Spring konfigurieren. Man hat jedoch ein Problem, wenn man n verschiedene Datasourcen hat, die zur Laufzeit ausgewählt werden müssen.

  • Die Konfiguration ist umfangreich und unübersichtlich
    • Pro Datasource ist ein Persistence-Unit notwendig, denn für die Ermittlung des richtigen EntityManager kann @PersistenceContext(unitName = “PU_NAME”) verwendet werden
    • Pro Datasource einen EntityManagerFactory mit der Verknüpfung zur PersistenceUnit
    • Pro EntityManagerFactory einen TransactionManager, ggf. mit Qualifier.
  • Die Wartung ist entsprechend nicht optimal. Pro neue Datasource müssen 3 Stellen angepasst werden
  • Das Transaction-Handling wird kompliziert. @Transactional muss nun immer den richtigen TransactionManager nutzen. Die Entscheidung soll natürlich zur Laufzeit passieren. @Transactional(“TX_MANAGER_NAME”) reicht nicht aus, da es eine statische Kopplung verursacht.

Eine andere Lösungsvariante wäre, statt einen EntityManager durch @PersitenceContext eher direkt den EntityManagerFactory durch @PersistenceUnit zu bekommen. Hier modifiziert man die EntityManager-Erstellung soweit, dass man den EntityManager mit der gewünschten Datasource bekommt. Falls sich bei den Datasourcen nur die User unterscheiden und Eclipselink verwendet wird, siehe u.a. [1].

Perfekt wäre jedoch eine Lösung, in der nur EntityManagerFactory, ein TransactionManager und ein PersistenceUnit verwendet wird. Zur Laufzeit soll dann der vorhandene EntityManagerFactory für die gewünschte Datasource einen EntityManager erstellen.

Das funktioniert. Der EntityManagerFactory erwartet eine Referenz auf die DataSource. Hier könnte man eine eigene Implementierung dieses Interface anbieten, welches die Unterscheidung bzgl. der verschiedenen Datasourcen durchführt. DataSource.getConnection() muss dann abhängig von fest definierten Eigenschaften die Connection von der richtigen Datasource zurückgeben. Um die Eigenschaften in der eigenen DataSource-Implementierung abzufragen, ist die Nutzung von ThreadLocal bzw. InheritableThreadLocal möglich.

Eine Spring-spezifische Lösung die in diese Richtung geht, ist mittels AbstractRoutingDatasource möglich [2]. Hier muss die abstrakte Methode determineCurrentLookupKey() implementiert werden. In dieser Methode muss die Logik verpackt werden, damit anhand des resultierenden Keys die richtige Datasource verwendet werden kann. Das Mapping zwischen Key und Datasource wird in AbstractRoutingDatasources#targetDataSources gehalten. Wird kein Key zurückgeliefert, wird die vorher definierte Standard-Datasource verwendet.

Eine Beispiel-Implementierung, welches mit Spring 3.x und jeweils mit Eclipselink und Hibernate funktioniert, sieht wie folgt aus:

Die relevante Spring-Konfiguration:

<bean id="baseDataSource" abstract="true">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
</bean>

<bean id="dataSource1" parent="baseDataSource">
<property name="user" value="${db.1.user}"/>
<property name="password" value="${db.1.pw}"/>
</bean>
<bean id="dataSource2" parent="baseDataSource">
<property name="user" value="${db.2.user}"/>
<property name="password" value="${db.2.pw}"/>
</bean>
<bean id="dataSource3" parent="baseDataSource">
<property name="user" value="${db.3.user}"/>
<property name="password" value="${db.3.pw}"/>
</bean>

<bean id="dynamicDatasource" class="info.center-of.spring.jpa.DynamicDataSourceRouting">
<property name="targetDataSources">
<entry key="${db.1.user}" value-ref="dataSource1"/>
<entry key="${db.2.user}" value-ref="dataSource2"/>
<entry key="${db.3.user}" value-ref="dataSource3"/>
</property>
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="datasource" ref="dynamicDatasource" />
<property name="jpaVendorAdapter">
<!-- Fuer Hibernate -->
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" p:showSql="true" generateDdl="true" />
<!-- Fuer Eclipselink
<bean class="org.springframework.orm.jpa.vendor.EclipselinkJpaVendorAdapter" p:showSql="true" generateDdl="true" />
-->
</property>
</bean>

<bean class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<tx:annotation-driven />

Die Klasse DynamicDataSourceRouting überschreibt die Methode zur Ermittlung des Keys. In unserem Fall ist dies immer der aktuelle/gewünschte DB-User:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class CustomerRoutingDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
return DBUserContextHolder.getDBUser();
}
}

DBUserContextHolder ist eine Klasse mit einer ThreadLocal-Variable, die in dem aktuellen Thread den DB-User hält

public class DBUserContextHolder {

private static final InheritableThreadLocal<String> contextHolder = new InheritableThreadLocal<String>();

public static void setDBUser(final String dbUser) {
contextHolder.set(dbUser);
}

public static String getDBUser() {
return (String) contextHolder.get();
}

public static void clear() {
contextHolder.remove();
}
}

Bevor der EntityManager nun erstellt wird, muss

DBUserContextHolder.setDBUser(&quot;dbUser1&quot;);

aufgerufen werden.

Mit dieser Lösung hat man “nur” eine enge Kopplung mit Spring. Die Lösung funktioniert mit verschiedenen ORMs.

 

[1]: Wikibook JPA: Provide each application user with a database user id

[2]: Spring Blog: Dynamic DataSource Routing

 

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • MisterWong
  • Technorati
  • Yigg
2 Responses leave one →
  1. praneet permalink
    April 13, 2011

    nicht funktioniert mit meinem Fall, kann u geben Code für Ihre Service-Klasse oder dao

  2. April 13, 2011

    Hi,

    die Implementierung ist eigentlich Unabhängig von der DAO- und Service-Klasse.
    Funktioniert Deine Anwendung auch ohne “Dynamic Datasource Routing”?

    Is it better to talk in English?
    The implementation of “dynamic datasource routing” is autonomous of the DAO or Service-class realization.
    What is the problem?

    Regards

    Haf

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS