Die Beiträge PostgreSQL: Partitionierung großer Tabellen Teil 1 und Teil 2 beschreiben die datenbankseitige Umsetzung dieser Performanzverbesserung. In einer Enterprise Java Umgebung ist eine Datenbank meist über ein JPA-Framework eingebunden und damit eng an die Anwendung gekoppelt. Dieser 3. Teil zu partitionierten Tabellen in PostgreSQL zeigt Ergänzungen, die beim Einsatz von Hibernate nötig werden.
Wer Performanzprobleme mit Hilfe der Partitionierung einer Tabelle in PostgreSQL in den Griff bekommen will, muss bei Hibernate mit einem zusätzlichen Hindernis rechnen. So installiert, wie in der Dokumentation beschrieben, schlägt ein Insert fehl. Denn dann prüft Hibernate mit dem Rückgabewert, ob das Statement wirklich eine Zeile eingefügt hat. Die Tabelle sollte die Zahl Eins zurückgeben. Dies bleibt aus, weil die getriggerte Funktion die Werte auschließlich auf die Kindertabellen verteilt. Sie sollte nichts in die Master-Tabelle einfügen. Deshalb wird im Teil 2 des Blogs „PostgreSQL: Partitionierung großer Tabellen“ beim Beispiel der Einfügefunktion der Return-Wert „NULL“ eingetragen. So gibt diese Master-Tabelle, die Hibernate „sieht“ aber „0“ als Anzahl der eingefügten Zeilen zurück statt der von Hibernate erwarteten „1“. Dies führt dann zu dem Fehler:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
Es gibt zwei verschiedene Herangehensweisen, das Problem zu lösen: Im Java-Code oder in der Datenbank.
Java ergänzen
Die empfohlene Methode, diesen Fehler zu vermeiden, ist das Abstellen der Überprüfung durch Hibernate. Das geht nur über die Annotation org.hibernate.annotations.SQLInsert direkt an der Entity.
Bei dieser Annotation gibt es das Argument „check=ResultCheckStyle.NONE“, welcher die Überprüfung abschaltet.
@SQLInsert erwartet immer ein Argument „sql“, welches das auszuführende Insert-Statement bestimmt. Für unseren Fall wollen wir das Statement selbst nicht ändern, schreiben es also, wie Hibernate es erzeugen würde:
[code title=”Hibernate Annotation”]@SQLInsert(sql = "insert into your_table_name (your_column_1, your_column_2) values (?, ?)")[/code]
In dem Beispiel fehlt das „check“-Argument, weil der DEFAULT-Wert bereits ResultCheckStyle.NONE ist.
Wer Weiteres über die DML-Annotationen von Hibernate erfahren will, kann dies bei RedHat nachlesen: JBoss_Enterprise_Application_Platform 5 Hibernate_Annotations_Reference_Guide
Datenbankseitige Lösung
Soll oder kann der Java-Code nicht geändert werden, dann lässt sich das Problem auch auf Datenbankseite lösen. In diesem Fall sollte die Trigger-Funktion den Wert zurückgeben, der eingefügt wurde. Also anders als in Teil 2 dieser Blogserie beschrieben, setzen wir RETURN NEW; ans Ende der Funktion. Damit wird die Zeile in die Master-Tabelle eingefügt, die so die erwartete „1“ als Rückgabewert liefert. Hibernate ist dann zufrieden, aber wir „verschmutzen“ so unsere Datenbank. Damit die Master-Tabelle leer bleibt, müssen wir einen zweiten Trigger hinterherschicken, der diesen Eintrag wieder löscht:
[code title=”Beispiel für einen Trigger:”]
CREATE TRIGGER clean_ partitions_master_table
AFTER INSERT
ON master_table
FOR EACH ROW
EXECUTE PROCEDURE partitions_master_table_cleanup_trigger();
[/code]
[code title=”Beispiel für eine Funktion:”]
CREATE OR REPLACE FUNCTION partitions_master_table_cleanup_trigger()
RETURNS trigger AS
$BODY$BEGIN
delete from only master_table;
RETURN NULL;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
[/code]
Mit der Ergänzung bleiben die Partitionierungen im gewünschten Zustand.
Sollten viele Inserts erfolgen, dann ist diese Methode, die statt einem Insert zwei plus ein Delete ausführt, aber nicht empfehlenswert. Sollte trotzdem diese Lösung nötig sein, könnten Bulk- oder Batch-Inserts direkt in die Tochtertabellen ausgeführt werden.
Aussicht auf einfachere Lösung
Die PostgreSQL-Entwickler arbeiten aktuell an einer sauberen Lösung für dieses Problem, bei der ein neues Feature den Insert-Trigger ersetzen kann. Im Postgres Wiki (Stand 30. Juli 2015) sind die Pläne beschrieben.