PersistenceContext Injection für alle!

Datenpersistierung mit e4 und Gemini JPA

Nepomuk Seiler

Eclipse e4 kommt! Unter anderem mit einer noch engeren OSGi-Integration und Dependency Injection. Die JPA-Spezifikation und die Referenzimplementierung EclipseLink nutzen keines dieser neuen Features. Deshalb stellt dieser Artikel Eclipse Gemini JPA/DBAccess und eine eigene DI Extension vor. Damit lässt sich die Anbindung von Datenbanken erleichtern.

Die JPA-Spezifikation für Java SE sieht vor, dass die Persistenzprovider-Implementierung, hier EclipseLink, in einem Klassenpfad mit den Entitäten und der persistence.xml liegt. Der Aufruf javax.persistence.Persistence.createEntityManagerFactory(unitName) durchsucht dann den Klassenpfad nach einem PersistenceProvider, indem er nach javax.persistence.spi.PersistenceProvider.xml sucht [1]. Der PersistenceProvider selbst sucht dann nach der persistence.xml oder orm.xml, die die gewünschte Persistence Unit ( PU) bereitstellt, was ebenfalls durch eine Suche im Klassenpfad realisiert ist.

Eclipse Magazin

Der Artikel „PersistenceContext Injection für alle!“ von Nepomuk Seiler ist erstmalig erschienen im

Eclipse Magazin 4.2012

OSGi Classloading

In einem OSGi-System gibt es keinen gemeinsamen Klassenpfad. Die verschiedenen Packages werden über Importanweisungen in der MANIFEST.MF hinzugefügt, und der Bundle-interne Classloader erhält damit Zugriff auf alle enthaltenen Klassen. Das bedeutet, dass ein PersistenceProvider-Bundle alle Packages mit Entitäten importieren müsste, um Zugriff auf die Klassen zu haben. Das ist in OSGi aber nicht praktikabel realisierbar. Eine Lösung wäre, alle JARs des PersistenceProviders in das Bundle mit den Entitäten und der persistence.xml zu packen und direkt den Provider zu instanziieren, also ohne den Persistence.createEntityManagerFactory aufzurufen. Das ist allerdings ein schlechtes Design, weil ein einfaches Aktualisieren des PersistenceProviders nicht möglich ist und jedes Bundle mit einer persistence.xml den kompletten PersistenceProvider in seinem Klassenpfad haben muss. EclipseLink bietet die Möglichkeit, einen speziellen Classloader in den Properties über den PersistenceUnitProperties.CLASSLOADER-Schlüssel zu setzen. Das führt aber zu äußerst instabilen Implementierungen, weil immer der richtige Classloader beim Erstellen der EntityManagerFactory verwendet werden muss.

Gemini JPA und DBAccess…

Gemini JPA stellt für dieses Classloading-Problem eine Lösung und die Referenzimplementierung der OSGi-Enterprise-Spezifikation [2] bereit. Es ersetzt auch die von EclipseLink selbst bereitgestellte Lösung. Gemini JPA nutzt die OSGi-Servicearchitektur und die Möglichkeit, den Bundle Header in der MANIFEST.MF zu erweitern. Beim Start von Gemini JPA werden alle Bundles nach der Meta-Persistence-Erweiterung im Bundle Header durchsucht. Standardmäßig werden alle bereits aktivierten Bundles neugestartet, um Classweaving zu ermöglichen. Danach wird in der Service-Registry nach einer DataSourceFactory gesucht, die den in der persistence.xml beschriebenen Treiber bereitstellt. Aktuell liefert Gemini DBAccess nur einen Treiber für Apache Derby mit. Erweiterungen für H2, HSQLDB [3] und MySQL [4] sind aber bereits in Vorbereitung. Nach dem erfolgreichen Laden des Treiber-Services erzeugt Gemini JPA zwei OSGi-Services.

Eigenen Datenbanktreiber bereitstellen

Die Auswahl an Datenbanksystemen, die Gemini DBAccess unterstützt, ist noch sehr übersichtlich. Es ist aber sehr einfach, selbst eine eigene DataSourceFactory, also einen Datenbanktreiber, bereitzustellen, wenn bereits ein JDBC Treiber existiert.

DataSourceFactory implementieren

Um einen eigenen Treiber bereitzustellen, wird das Serviceinterface org.osgi.service.jdbc.DataSourceFactory implementiert. Gemini JPA stellt eine abstrakte Klasse mit Standardimplementierungen bereit. Das JDBC Treiber JAR sollte in ein eigenes Bundle gepackt werden. Das DataSourceFactory Bundle importiert die exportierten Packages. Dadurch wird der DataSourceFactory Service mit der Treiber-Version gekoppelt.

Service registrieren

Nach der Implementierung muss der Service registriert werden. Das kann deklarativ oder im Activator geschehen. Zwei Service-Properties müssen gesetzt werden, damit die DataSourceFactory richtig identifiziert werden kann.

  • DataSourceFactory.OSGI_JDBC_DRIVER_NAME
  • DataSourceFactory.OSGI_JDBC_DRIVER_CLASS

Optional kann auch noch die Property OSGI_JDBC_DRIVER_VERSION gesetzt werden, um Treiber für spezielle Versionen bereitzustellen.

Der EntityManagerFactoryBuilder (EMFB) -Service wird immer instanziiert, falls die persistence.xml alle notwendigen Informationen enthält, um eine Datenbankverbindung aufzubauen. Andernfalls wird eine Exception geworfen. Mit diesem Service lassen sich EntityManagerFactories für die spezifizierte PU erzeugen. Jeder EMFB lässt sich über die Serviceproperty osgi.unit.name, die den PU Namen enthält, eindeutig identifizieren. Ist die korrekte DataSourceFactory bereits registriert oder der JDBC Treiber liegt im Klassenpfad des Persistence Bundles vor, wird ebenfalls ein EntityManagerFactory (EMF) -Service registriert, der direkt genutzt werden kann, um einen EntityManager zu erzeugen. Dieser Service wird in der Regel verwendet, um die Datenbank anzusprechen. Der EMF-Service enthält dieselben Service-Properties wie der EMFB [5].

Als PersistenceProvider wird momentan nur EclipseLink unterstützt, weitere sind nicht geplant. Wichtig ist, dass Gemini JPA nichts mit der EclipseLink-OSGi-Erweiterung zu tun hat und beide auch nicht gleichzeitig genutzt werden sollten. Gemini JPA ist die Referenzimplementierung der OSGi-JPA-Spezifikation und sollte daher gegenüber der proprietären EclipseLink-Lösung vorgezogen werden.

… mit Eclipse RCP

Der EMF-Service und der EMFB-Service sind normale OSGi-Services, die über die Interfaces EntityManagerFactory und EntityManagerFactoryBuilder registriert sind. Diese können einfach in deklarative Services eingebunden oder über einen ServiceTracker angesprochen werden. Über die Service-Property osgi.unit.name können die Services anhand des Namens der PU unterschieden werden, wie in Listing 1 und 2 beschrieben.

Listing 1

new ServiceTracker(
 IEntityManagerService.class, "(osgi.unit.name="+ unitName +")", null);
Listing 2

public class DeclarativeService {
  //...
protected void bindService(IEntityManagerFactoryService, Map properties) {
    //Ein Filter kann auch in der component.xml für dieses property angelegt werden
    String unitName = properties.get("osgi.unit.name");
    //...
  }
}

Ein großes Problem, das sich im Zusammenspiel mit Eclipse RCP (3.x und e4) ergibt, ist, dass Gemini JPA alle Bundles mit einer persistence.xml neu startet, um Classweaving zu ermöglichen. Durch das Neustarten eines Bundles werden alle davon abhängigen Bundles ebenfalls neu gestartet. In Eclipse 3.x führt das zu einer Neuregistrierung aller definierten Extensions, insbesondere der von UI Extensions. Dadurch entstehen Race Conditions, egal, ob die Eclipse RCP Application UI vor oder nach der Neuregistrierung startet. Je nachdem wird die UI richtig, teilweise oder gar nicht angezeigt. Mit Eclipse e4 treten diese Fehler durch das Application Model nicht auf, unterschiedliche, nicht zuverlässig reproduzierbare Exceptions hingegen schon.

Die vom Gemini-Projekt vorgeschlagene Lösung lautet, den Startlevel der Bundles mit persistence.xml höher zu setzen als den des Gemini JPA Bundles. Dabei ist zu beachten, den Startlevel des Gemini DBAccess Bundles, bzw. aller Bundles, die eine DataSourceFactory bereitstellen, niedriger als den des Gemini JPA Bundles zu setzen. Das sieht dann beispielweise so aus:

  • default startlevel = 4
  • de.mukis.entity = 4
  • org.eclipse.gemini.jpa = 3
  • org.eclipse.gemini.dbaccess = 2

Dieser Ansatz funktioniert leider nicht in allen Fällen. In Eclipse e4 werden durch das Neustarten BundleStateExceptions geworfen. In Eclipse 3.x läuft es ebenfalls instabil. Deswegen erweitert man diesen Vorschlag und deaktiviert Weaving in der persistence.xml mit der Property eclipselink.weaving und das Neustarten der Bundles durch das VM-Argument -DREFRESH_BUNDLES=false. Durch das Deaktivieren des Weavings verliert man möglicherweise Performance, allerdings erhält man dafür ein stabil laufendes System. Gemini JPA nutzt den standardisierten OSGi Weavinghook. Eine stabile Lösung mit Weaving ist sicherlich möglich, es fehlt aber noch an Aufmerksamkeit der Entwicklergemeinde.

Geschrieben von
Nepomuk Seiler
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.