Suche
Kolumne: EnterpriseTales

Zehn Jahre JPA: Von einem (Standard), der auszog, Veränderung einzuleiten

Arne Limburg

Am 11. Mai 2006 wurde der EJB-3.0-Standard veröffentlicht und mit ihm die Version 1.0 des Java Persistence API, kurz JPA. Und so hatte JPA vor einem knappen Dreivierteljahr seinen ersten runden Geburtstag. Zehn Jahre sind in der IT-Welt eine halbe Ewigkeit. Wie hat der JPA-Standard diese Zeit überstanden? Wo kam er her? Was hat er bewirkt? Und welche Bedeutung hat er heute und in der Zukunft?

Seit den 70er-Jahren des letzten Jahrhunderts gibt es relationale Datenbanken, spätestens in den 90er-Jahren setzten sie sich endgültig im Enterprise-Computing durch. In Java gibt es seit 1996 die Möglichkeit, standardisiert auf relationale Datenbanken zuzugreifen. Da wurde nämlich die Version 1.0 des JDBC-API veröffentlicht. Damit konnte zwar auf eine relationale Datenbank zugegriffen werden, als Ergebnis bekam man aber eine relationale Sicht auf die Daten. Da Java eine objektorientierte Programmiersprache ist, ergab sich bereits damals die Herausforderung, diese Daten nach dem Laden in Objekte zu mappen und Objekte beim Speichern wieder in die relationale Repräsentation zu überführen. Man benötigt ein Object-relational Mapping.

Herausforderungen des Object-relational Mappings

Dass beim Object-relational Mapping (ORM) Datenbankspalten auf Objektattribute gemappt werden, ist unmittelbar einsichtig. Herausforderungen sind aber das Mapping von Java-Objektbeziehungen auf Datenbankfremdschlüsselbeziehungen, das Mapping von Ableitungshierarchien und das Mapping eines feingranularen Datenmodells auf relationale Datenbanken, bei denen häufig eine gröbere Granularität vorzufinden ist, also weniger Tabellen. Auch die Gleichheit ist eine Herausforderung: Während in Java in der Regel zwei Objektreferenzen als gleich definiert werden, wenn sie auf dasselbe Java-Objekt zeigen, gibt es in einer relationalen Datenbank häufig einen Primärschlüssel. Wenn also beim ORM zweimal dieselbe Zeile gemappt wird, muss beim Vergleich beachtet werden, dass gegebenenfalls zwei unterschiedliche Java-Objekte dieselbe Zeile repräsentieren.

Lesen Sie auch: Softwarearchitektur für Innovation: Der heilige Gral

Es gab schon früh Bemühungen, den Vorgang des ORMs in Java zu standardisieren. Ein erster Versuch war der JDO-Standard, der im Jahr 1999 ins Leben gerufen und 2001 verabschiedet wurde. Er wurde aber nicht als J2EE-Standard übernommen. Stattdessen entschied man sich, EJB Entity Beans als Ansatz für ORM zu standardisieren. Diese erwiesen sich in der Praxis als schlecht handhabbar und viel zu schwergewichtig, weshalb sie sich nicht durchsetzten. Als Folge daraus entstanden um die Jahrtausendwende in vielen Unternehmen selbstentwickelte ORM-Lösungen von unterschiedlicher Qualität. Zum Teil sind diese Lösungen in Unternehmen heute noch im Einsatz, was nicht selten ein größeres Problem in puncto Pflege und Wartung darstellt.

Der Spring-Hibernate-Stack als Alternative

EJB Entity Beans waren nur ein Teil des J2EE-Standards, der nicht gut funktionierte. Der gesamte EJB-Stack war zu dieser Zeit schwergewichtig, was dazu führte, dass Entwickler sich häufig mehr mit der Technologie auseinandersetzen mussten, als sich darum zu kümmern, die eigentlich geforderte Businesslogik umzusetzen. Es entstanden viele Technologiepatterns, die eigentlich alle nur dazu da waren, Unzulänglichkeiten der J2EE-Technologie zu umschiffen. Letztendlich waren es Workarounds für Designprobleme von J2EE.

Als Konsequenz daraus entwickelte sich eine Gegeninitiative, die sich vom J2EE-Standard abwandte und auf Open-Source-Lösungen setzte. Diese ermöglichten eine deutlich höhere Entwicklungsperformance. Ausgelöst wurde diese Bewegung durch das Buch von Rod Johnson und Jürgen Höller „J2EE Development without EJB“ aus 2004 und dem daraus entstehenden Spring-Framework. Im Zuge dieser Bewegung etablierte sich auch das ORM-Framework Hibernate. Dieser Spring-Hibernate-Stack funktionierte so gut, dass ab 2006 jeder, der die Chance dazu hatte, seine Software nicht mehr mit J2EE, sondern mit eben diesem Stack realisierte – und das aus gutem Grund.

JPA als Symbol für den Paradigmenwechsel

Ein großes Problem des J2EE-Standards war seit jeher seine Entstehung am Reißbrett. Große Firmen überlegten sich in der Theorie, was ein Enterprise-Standard wohl enthalten sollte und womit sie ihre Enterprise-Server besser verkaufen konnten. Häufig waren die Ergebnisse Lösungen für Probleme, die es in der Praxis gar nicht gab. Andersherum gab es für die Probleme aus der Praxis keine standardisierten Lösungen.

Mit EJB 3.0 und JPA 1.0 fand in diesem Punkt ein entscheidendes Umdenken statt. Der Druck, der aus der Open-Source-Welt, insbesondere der Spring-Hibernate-Community, auf den Standard ausgeübt wurde, führte dazu, dass man in den J2EE-Standardisierungskomitees bemerkte, dass es so nicht weitergehen konnte. Anstatt Standards am Reißbrett zu entwerfen, wurde nun in der Community geschaut, was tatsächlich benötigt wurde, und vor allem, was in der Praxis funktionierte. Im Bereich der Komponententechnologien war das die von Spring propagierte Dependency Injection und im Bereich der Persistenz eben der Ansatz, den Hibernate verfolgte. Während die Übernahme der Dependency Injection in den Standard in EJB 3.0 noch nicht gut funktionierte, weil man sich aus Abwärtskompatibilitätsgründen dafür entschied, das schwergewichtige Laufzeitmodell von EJB beizubehalten, gelang dieser Schritt beim ORM deutlich besser. Man schaute sich das in der Praxis erfolgreiche Hibernate-Projekt an und ließ darauf den Standard für die neue Persistenztechnologie (JPA) basieren. Hibernate wurde eine Implementierung des neuen Standards, wenn auch nicht die Referenzimplementierung. Das wurde Oracles Toplink. Der Schritt weg vom Reißbrettstandard und hin zur Standardisierung der Lösungen aus der Praxis gelang mit JPA auch deshalb so gut, weil man mit diesem Standard gewissermaßen auf der grünen Wiese begann und die alten EJB Entity Beans komplett unbeachtet ließ. Die Version 1.0 des JPA-Standards war übrigens keine eigene Spezifikation, sondern als Teil der EJB-3.0-Spezifikation der direkte Ersatz von EJB Entity Beans. Aus Gründen der Abwärtskompatibilität existierten EJB Entity Beans zwar zunächst parallel, wurden aber nicht weiterentwickelt.

Sehenswert: JAX TV: JPA – Architekturkonzepte und Best Practices revisited

Gewissermaßen um diesen Paradigmenwechsel symbolisch zu unterstreichen, wurde J2EE umbenannt in Java EE. Ein Grund dafür war sicherlich auch, dass J2EE viel verbrannte Erde hinterlassen hatte. So erhoffte man sich mit dem neuen Namen gewissermaßen einen Neuanfang, der zumindest mit JPA auch tatsächlich gelang. JPA war bereits mit der Version 1.0 ein sehr ausgereifter Standard, auf dessen Basis direkt ORM in produktivem Code eingesetzt werden konnte. Der Standard war auch schon so vollständig, dass man in den meisten Fällen ohne proprietäre Erweiterungen auskam.

Im Rest von Java EE gelang dieser große Wurf erst mit Java EE 6, als auch auf der grünen Wiese mit CDI ein neuer Komponentenstandard vorgestellt wurde. Aufgrund des großen Erfolgs von JPA 1.0 ging man mit Java EE 6 einen weiteren bemerkenswerten Schritt: Die alten EJB Entity Beans wurden als pruned deklariert und damit gewissermaßen aus dem Standard entfernt. Das war der erste Bruch mit der Abwärtskompatibilität innerhalb von Java EE.

Architektonische Herausforderungen

Eine große Herausforderung beim ORM ist das Lazy Loading. Wenn man ein Objekt aus der Datenbank lädt, möchte man seine Beziehungen erst laden, wenn man sie tatsächlich braucht. Ansonsten kann es passieren, dass man ein Objekt aus der Datenbank holt und dadurch nebenbei die gesamte Datenbank in den Speicher lädt. Das ist aus Performance- und Arbeitsspeichergründen nicht sinnvoll. Hibernate unterstützte Lazy Loading bereits sehr früh, allerdings nur, solange die Hibernate-Session, das Pendant zum EntityManager in JPA, geöffnet ist. Ungünstigerweise ist die Hibernate-Session nicht thread-safe, genau wie der EntityManager, sodass nicht aus mehreren Threads auf ihn zugegriffen werden darf.

Zu Beginn der Spring-Hibernate-Zeit, als der Stack bei klassischen Webanwendungen in der Regel mit Struts im Frontend ergänzt wurde, war das noch nicht so ein großes Problem. Die Ajax-Technologie war noch nicht so weit verbreitet und Web Requests kamen in der Regel sequenziell beim Server an und konnten auch sequenziell verarbeitet werden. Frameworks wie Orchestra konnten dafür sorgen, dass die Hibernate-Session oder der EntityManager über mehrere Requests hinweg geöffnet blieben und so Lazy Loading möglich war.

Mit dem Aufkommen von Ajax und dem Tabbed Browsing, was ab 2006 in praktisch jedem Browser verfügbar war, konnte man sich in einer Webanwendung nicht mehr sicher sein, dass nicht mehrere Requests eines Benutzers gleichzeitig vom Server verarbeitet werden mussten. Eine Hibernate-Session oder ein EntityManager mussten immer genau einem Request zugeordnet werden, um die Thread-Sicherheit zu garantieren. Zu dieser Zeit entwickelte sich das Open-Session-In-View-Pattern, bzw. Open-Entity-Manager-In-View-Pattern. Die Idee des Patterns ist, dass alle Entitäten, die über mehr als einen Request hinweg benötigt werden, in der Websession gespeichert werden. Zu Beginn eines jeden Requests wird die Hibernate-Session bzw. der EntityManager geöffnet, alle Entitäten werden damit verbunden, sodass im gesamten Request Lazy Loading funktioniert. Erst am Ende des Requests werden Hibernate-Session und EntityManager wieder geschlossen. Das einzige Problem in Hibernate war die damals berühmt-berüchtigte Fehlermeldung „an entity is associated with two open sessions“. Das passierte immer dann, wenn versucht wurde, in zwei parallelen Requests dieselbe Entität mit der zum jeweiligen Request gehörenden Hibernate-Session zu verbinden. Dieses Problem umging man in JPA elegant, denn es ist nicht möglich, eine Entität, die einmal detached war, also vom EntityManager entkoppelt, wieder mit dem EntityManager zu verbinden. Es ist lediglich möglich, eine Kopie mit dem aktuellen EntityManager über merge zu verbinden. Das ist der Grund, warum man bei der Verwendung von merge immer den Rückgabewert, nämlich die besagte Kopie, weiterverwenden sollte. Zwei parallele Threads erhalten so immer zwei unterschiedliche Kopien derselben Entität. Eventuelle Konflikte beim parallelen Schreiben können mittels Optimistic Locking aufgelöst werden.

In der ursprünglichen Implementierung erwies sich das Open-Session-In-View-Pattern leider als Anti-Pattern, weil das Öffnen von Hibernate-Sessions und EntityManager in der Standardkonfiguration auch immer das Starten einer Transaktion zur Folge hat. Das Pattern sorgt also dafür, dass während des gesamten Requests, also auch während der oft langen Renderingphase, eine Transaktion aktiv ist. In Hochlastszenarien führte das nicht selten zu Engpässen bei der Datenbank.

W-JAX
 
Arno Haase

Neues aus der Java-Trickkiste

mit Arno Haase (Arno Haase Consulting)

Norman Lahme-Hütig

Java-8-Nachlese – Wie hat Java 8 die Java-Welt verändert?

mit Klaus Kreft (Angelika Langer Training/Consulting)

Der JPA-Standard bietet aber eine Möglichkeit, dieses Pattern zu umgehen. Es bietet nämlich den so genannten Extended EntityManager. Dieser ist unabhängig von einer Transaktion und ermöglicht lesenden Datenbankzugriff auch außerhalb einer Transaktion. Er kann also den gesamten Request lang geöffnet sein, ohne dabei dauerhaft Datenbankressourcen zu blockieren. Das Transaktionshandling kann vollständig entkoppelt erfolgen, was allerdings weitere Probleme birgt. In der Standardkonfiguration verbindet sich der EntityManager nämlich automatisch mit einer Transaktion, sobald eine aktiv ist. Das bedeutet, sobald ich z. B. in einem Wizard eine Entität mit dem EntityManager verbinde, um Lazy Loading zu ermöglichen, muss ich gleichzeitig aufpassen, dass sich dieser nicht auch mit einer Transaktion verbindet. Ein automatisches Schreiben der Entität in die Datenbank wäre die Folge, sobald die Transaktion committet würde. Und das kann dann inmitten des Wizards passieren, obwohl das Schreiben erst am Ende gewünscht ist. Die neueste Version 2.1 des JPA-Standards schafft aber auch hier Abhilfe. Sie führt das Konzept des unsynchronized EntityManagers ein. Dieser verbindet sich eben nicht automatisch mit einer laufenden Transaktion, sondern muss manuell mit ihr über joinTransaction verbunden werden. Das kann beim Abschließen des Wizards geschehen. Ein unbeabsichtigtes vorheriges Speichern kann nicht passieren.

Mittlerweile werden serverseitige Webanwendungen immer häufiger durch reine clientseitige Anwendungen ersetzt, in der Regel Single-Page-Applications, die nur via REST mit dem Server kommunizieren. Auch in den immer mehr aufkommenden Microservices-Architekturen etabliert sich die Kommunikation über REST. Da REST per Definition ein statusloses Protokoll ist, ist das Offenhalten des EntityManagers über mehr als einen Request hinweg nicht mehr sinnvoll. Das lässt die beschriebenen Probleme häufig obsolet werden.

Gestern, heute und morgen

Zehn Jahre sind eine lange Zeit in der IT. Die (IT-)Welt entwickelt sich weiter, und das nicht nur im Bereich der beschriebenen Webtechnologien. Mit den immer größer werdenden Datenmengen kommt es immer häufiger zu der Situation, dass relationale Datenbanken nicht mehr in der Lage sind, diese in adäquater Zeit zu verarbeiten. Zusätzlich sind Transaktionen, und insbesondere die verteilten Transaktionen, die der Java-EE-Standard bietet und die es ermöglichen, mehrere Datenquellen zu einer gemeinsamen Transaktion zu verbinden, häufig ein Performanceproblem. Neue Datenbanktechnologien, die so genannten NoSQL-Datenbanken, die speziell für diese großen Datenmengen entwickelt wurden und die in der Regel auf eine bestimmte Art von Daten spezialisiert sind, sind auf dem Vormarsch. In ihnen gibt es häufig keine Transaktionen im klassischen Sinn. Die ACID-Kriterien werden durch das Konzept der Eventually Consistency ersetzt, bei dem zwar garantiert wird, dass die Datenbank irgendwann konsistent ist, nicht aber, dass das bereits direkt nach Abschluss der Schreiboperation der Fall ist.

Lesen Sie auch: EnterpriseTales: Von der klassischen Webanwendung zur Multi-Channel-Architektur

Der JPA-Standard ist eng mit dem Konzept der klassischen Transaktionen verbunden. So ist z. B. definiert, dass Schreiboperationen nur innerhalb einer Transaktion stattfinden dürfen. Auch setzt er stark auf das Konzept von Tabellen und Spalten. Das existiert in vielen NoSQL-Datenbanken nicht. Vor diesem Hintergrund ist es unrealistisch und auch nicht sinnvoll, den JPA-Standard auf NoSQL-Datenbanken ausweiten zu wollen. Der JPA-Standard wird daher zwangsläufig an Bedeutung verlieren. Das heißt natürlich nicht, dass relationale Datenbanken demnächst verschwinden. Vielmehr werden sie auch auf absehbare Zeit in vielen Businessanwendungen eine große Rolle spielen. Und natürlich ist JPA in diesen Anwendungen weiterhin eine gute Wahl als Persistenztechnologie.

Fazit

JPA ist ein ausgereifter Standard, der auch in der Praxis sehr gut funktioniert und etablierte Pattern bietet. Die erste Version war ein Paradigmenwechsel in der Definition von Enterprise-Java-Standards, weg vom Reißbrett und hin zu einer Beteiligung der Community. Mittlerweile hat sich aber die Welt im Enterprise Computing weiterbewegt. Relationale Datenbanken haben an Bedeutung verloren, und auch große, schwergewichtige Application Server müssen immer mehr leichtgewichtigen Microservices-Architekturen weichen. Verteilte Transaktionen im technischen Sinne weichen dabei globalen Businesstransaktionen, die mittels vieler kleiner Transaktionen in Microservices und mit dem Eventually-Consistency-Pattern abgebildet werden. Fachliche Compensation spielt eine immer größere Rolle und löst das technische Transaktions-Rollback ab. In diesem Zuge wird auch der JPA-Standard als allgemeiner Persistenzstandard in Enterprise-Architekturen an Bedeutung verlieren. Nichtsdestotrotz wird er, auch aufgrund seines Reifegrads, noch lange im Einsatz sein. Er ist nach wie vor immer dann eine gute Wahl und wird es auch in den nächsten Jahren bleiben, wenn man sich für eine relationale Datenbank als Persistenztechnologie eines Java-basierten Servers entscheidet. Das gilt auch innerhalb eines Microservice.

Geschrieben von
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Kommentare

Schreibe einen Kommentar

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