The posts PostgreSQL: Partitioning big tables Part 1 and Part 2 describe the implementation of this performance measure from the database view. In an enterprise Java environment often a database is integrated by a JPA framework and closely coupled with the application. This 3rd part about partitioned tables in PostgreSQL shows additions necessary when using Hibernate.
Solving performance issues with the partitioning of tables in PostgreSQL meets with an unobvious obstacle when using Hibernate. An insert will fail if the installation is realized as described in the documentation. The reason is the check Hibernate performs to verify if the statement really added a row. The table should return the digit One. This does not happen because the triggered function distributes the inserts only into the child tables. It should insert nothing into the master table. Thats because in the example in the second part of this series the insert function returns “NULL”. The side effect on the master table, which is the only one visible to Hibernate, is that it returns a Zero instead of the “1” Hibernate expects. This leads to the following error:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update ; actual row count: 0; expected: 1
We have two different approaches to solve this problem: In the Java code or inside the database.
The recommended method to avoid this error is to suppress the row count check of Hibernate. You can do this with the annotation org.hibernate.annotations.SQLInsert directly at the entity.
This annotation has the argument „check=ResultCheckStyle.NONE“, which switches off the verification.
@SQLInsert needs always the argument „sql“, which determines the insert statement to execute. In our case we don’t want to change the statement itself, so we write it as Hibernate would:
@SQLInsert(sql = "insert into your_table_name (your_column_1, your_column_2) values (?, ?)")
In our example, the “check” argument is missing because the DEFAULT value already sets the ResultCheckStyle.NONE.
For a more detailed view on this aspect and about other Hibernate DML annotations read RedHats JBoss_Enterprise_Application_Platform 5 Hibernate_Annotations_Reference_Guide.
Database only solution
If the Java code should not or cannot be changed, then we have to solve the problem on the database side. In this case, the inserting trigger function must return the value to insert it into the master table. Contrary to the description in the second part of this blog series, we have to set RETURN NEW; at the end of the function to achieve this. The insert causes the master table to return the attended value “1”. This satisfies Hibernate, but we pollute our database. To keep an empty master table we have to set a second trigger, which deletes the entry immediately afterwards:
CREATE TRIGGER clean_ partitions_master_table AFTER INSERT ON master_table FOR EACH ROW EXECUTE PROCEDURE partitions_master_table_cleanup_trigger();
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;
With this addition, the partitioned tables stay in the desired state.
If there are many inserts at a time, then this method, which executes instead of one insert two, plus one delete, is not recommended. If you cannot avoid this solution, bulk or batch inserts should be made directly to the child tables.
Simpler solution in view
The PostgreSQL developers are working actually on a better solution for this problem which substitutes the insert trigger by a new feature. In the Postgres Wiki (read the 30th July 2015) they describe their plans.