Skip to content

JPA: Dynamic Datasource Routing

2011 März 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:

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

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

Bevor der EntityManager nun erstellt wird, muss

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 it!
  •  
  •  
  •  
  •  
  •  
  •  
4 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

  3. urks permalink
    Januar 17, 2013

    hi,
    wie kann ich denn nun die datasource zur runtime ändern? jede meiner DAO-Klassen verwendet entweder datasource 1 oder 2, ich verwende den EnityManager in etwa so:

    @PersistenceContext(unitName = „pu“)
    public EntityManager entityManager;

    public Session getSession() {
    return ( (Session) entityManager.getDelegate() );
    }

    public void test(){
    DetachedCriteria mainCriteria = DetachedCriteria.forClass( Test.class );
    //(….)
    mainCriteria.getExecutableCriteria( getSession() ).list();
    }

  4. Februar 13, 2013

    Als DataSource wird hier eine Implementierung von AbstractRoutingDataSource (hier: DynamicDataSourceRouting) verwendet. D.h. der EntityManagerFactory nutzt diese AbstractRoutingDatasource.
    Die AbstractRoutingDatasource selbst kennt alle möglichen Datasource-Varianten. Jede dieser Datasource wird durch ein Key identifiziert. Bei mir im Beispiel durch User-IDs.
    Mittels der eigenen Klasse DBUserContextHolder wird im lokalen Thread der richtige User/Key gemerkt. Wenn dann der EntityManagerFactory einen EntityManager von einer Datasource generieren will, wird hier die AbstractRoutingDatasource.determineCurrentLookupKey() aufgerufen. Diese liefert den richtigen Key – was man vorher explizit in dem lokalen Thread gesetzt hat. Durch diesen Key wird in AbstractRoutingDatasource die richtige Datasource verwendet.

    Verstanden? 😉

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