Kategorie-Archiv: JPA

Datenbanken sind langsam

Klar sind sie das. Datenbanken machen IO und IO ist generell nicht so wahnsinnig schnell. Und Datenbanken sind unendlich kompliziert und generell mit enorm viel Wartung verbunden und eigentlich total Überflüssig.

Für diese Probleme sehe ich bei meinen Codereviews immer wieder mehr oder minder originelle Lösungen.

Lösung: Alles im Speicher

Man verwaltet einfach alle Daten in einem großen Objektbaum, der dann hin und wieder auf die Festplatte serialisiert wird. Bei Programmstart wird das serialisierte Objekt mit allen Daten einfach geladen und Rock ‘n’ Roll.

Das funktioniert von mir aus einigermaßen mit der berühmten “Video Verwaltung”, die man gerne mal während des Studiums baut, und mag auch noch in kleinen Startups funktionieren, die einen Onlineshop mit 1000 Artikeln haben, aber da liegt dann auch schon das Problem: Bleibt es bei 1000 Artikeln, ist das Startup irgendwann Geschichte und die Lösung hat bis zum Schluss funktioniert. Der schlimmere Fall: das Startup wächst und irgendwann hat man 1 Mio Artikel. Die passen jetzt irgendwie nicht mehr in den Speicher. Mal ganz davon zu schweigen, dass das Serialisieren eines riesigen Objektbaums auf die Festplatte nicht wirklich transaktional ist. Das bedeutet, wenn etwas schief geht, geht es schief und die Daten sind weg. Konkurrierende Schreibzugriffe (Datensätze werden von mehreren Personen gleichzeitig bearbeitet) sind zwar total gut mit Time Slots zu lösen (von 9-11 Uhr darf nur die eine Abteilung ihre Daten bearbeiten, von 11-13 Uhr die nächste), aber das ist bei vielen Datensätzen nicht mehr wirklich praktikabel.

Also merke: Die Problemstellung der Video Verwaltung als Hausaufgabe während des Studiums war dazu da, grundlegende Datenstrukturen zu erklären und nicht als Lösungsvorschlag für kommerzielle Anwendungen.

Lösung: Ich suche alles selbst

Spätestens seit Hibernate ist es total einfach, Objekte statt direkt auf die Platte in eine Datenbank zu schreiben. Das ist schon mal gar nicht so schlecht. Der Vorteil ist, dass man nun Datensätze einzeln und in einer Transaktion speichert. Wenn da mal was nicht klappt, fehlt ein Datensatz, die restlichen Daten sind noch intakt. Auch können jetzt alle Abteilungen gleichzeitig an den Daten arbeiten. Das beste aber, der Code sieht fast wie normales Java aus. Die bösen Sachen übernimmt beispielsweise JPA für uns. Und da das alles so total nach Java aussieht und man total bekannte Datenstrukturen wie Listen aus der Datenbank kriegen kann, kann man da mit der hübschen for-Schleife aus Java 5 durchiterieren und sich die Daten mit if-Abfragen raus suchen, die einen interessieren.   Alles was man braucht, ist für jede Tabelle eine “finde Alles” Methode und ab geht’s. Man kann sogar total nett mit dem Collection Framework seine Daten nach eigenen Kriterien sortieren.

Das ganze hat nur ein Problem: es ist total ineffizient und produziert Unmengen von absolut unnötigem Code.

Datenbanken machen Datensachen!

Eben weil man meist nur bestimmte Datensätze braucht, und diese nach bestimmten Kriterien sortiert sein müssen, haben vor ungefähr 500 Jahren schlaue Computerwissenschaftler die Abfragesprache (Query Language) erfunden. Ganz, ganz kalter Kaffee? Das ist schon so lange her, das kann heute gar nicht mehr rocken?

Viel, viel schlimmer: Die Sache wird seit Generationen an allen Ecken und Enden verbessert, aktualisiert, modernisiert. Man könnte davon ausgehen, dass es ziemlich schwer wird, sich als Allrounder mit einem Menschen anzulegen, der den ganzen Tag nichts anderes macht, als sich mit effizienter Datenhaltung zu beschäftigen. Im Fall von Datenbanken lagt man sich da aber mit Generationen von Entwicklern an, die unglaublich viele Arbeitsstunden in die Technik versenkt haben – und ja, die ganze Mühe kommt auch wieder raus, wenn man seine Abfragen über die dafür vorgesehene Sprache formuliert.

Klar, die muss man dafür erst mal lernen, aber der Aufwand dafür ist deutlich geringer, als das Rad jedes mal neu zu erfinden. Ein netter Einstieg in SQL ist beispielsweise Einführung in SQL. Von SQL zur Abfragesprache von JPA ist es kein weiter Weg und ein bisschen SQL können, hat noch niemandem geschadet.

Datenbanken können das wofür sie gebaut sind unverschämt gut: Daten verwalten. Und sie sind beispielsweise in Form von MySQL oder Postgesql frei verfügbar.

Selbst die klassische Video Verwaltung lässt sich super mit einer Datenbank erledigen. Natürlich will man für so eine einfache Desktopanwendung nicht unbedingt einen Datenbankserver installieren. Dafür gibt es dann embedded Datenbanken, wie z.B. DerbyDB.

Wenn man also Sachen mit Daten machen muss, nimmt man sich eine Datenbank – die kann das schon.

JPA Lifecycle Events

Lifcycle Event Aufrufe können mitunter etwas verwirrend sein, wie ich jüngst feststellen durfte. In folgendem Beispiel nehmen wir eine einfache OneToMany Relation:

Klassendiagramm

Hierbei sind die onSave Methoden jeweils als Livecyclelistener annotiert:

@PrePersist
@PreUpdate
private void onSave() {
    System.out.println("about to save MainEntity " + id);
}

Weiterhin ist die Relation zu SubEntities mit CascadeAll in MainEntity annotiert, damit die Abhängigen Entities automatisch gespeichert werden.

Zum Testen erzeugen wir als erstes einmal eine MainEntity mit einer SubEntity:

MainEntity me = new MainEntity(Long.valueOf(1)); // Id
Collection col = new ArrayList();
col.add(new SubEntity(me));
me.setSubEntities(col);
System.out.println("persisting...");
persist(me); // do persist stuff

Bei dem Aufruf wird zuerst der PrePersist Callback von MainEntity und danach von SubEntity. Die Ausgabe des Programms ist:

persisting...
about to save MainEntity 1
about to save SubEntity 1

Das ist auch genau das, was ich erwartet hätte. So könnte man vor dem Speichern die Abhängigen Collections aufräumen (z.B. die Rückrelation zu MainEntity eintragen).

Als nächstes erweitern wir das Programm um einen merge Aufruf. Vor dem Merge wird eine neue Entity zu der Collection zugefügt.

col.add(new SubEntity(me));
System.out.println("merging...");
merge(me);

Die Ausgabe des Programms für diesen Teil ist:

merging...
about to save SubEntity null
about to save MainEntity 1

Ups, das war nicht das, was ich erwarten würde. Wenn zu einer Existierenden Entity ein weiteres Element zu der OneToMany Collection zugefügt wird, so wird der Entsprechende Callback vor dem UpdateCallback der MainEntity aufgerufen.
Damit hat sich die Idee vom automatischen aufräumen von Collections erledigt.
Das Ganze funktioniert genau so, wenn eine attached MainEntity um eine SubEntity erweitert wird. Werden hingegen nur SubEntities, die bereits existieren aktualisiert, wird der Callback für diese erst nach der MainEntity aufgerufen.
Getestet mit TopLink und EclipseLink mit identischen Ergebnissen.