A Neate Blog


15 April 2020

Using Persistable for JPA Performance Improvements

Tags: Java - JPA - Hibernate - Spring - Database - JDBC


Using JPA to interact with a database is, and always has been, heavily popular for Java applications; this post contains an example of how implementing persistable results in performance improvements for certain use-cases that might not be commonly known. This is particularly useful if you have little to no control over the database schema or it isn’t designed that well.

I’m sure many of us have been in the situation where we’re forced to use a specific table structure that can’t be changed for some obscure reason, in my case this often includes composite keys and an inherited ID structure.

The use-case here is quite simple, there is a parent and child table which act as a staging table whose records are processed at a later date by another program.

Parent Table:

bu_type bu_code supplier_number supplier_type additional_field_1
ORG 123 456 SUP FIELD1
ORG 456 789 SUP FIELD1

Child Table:

bu_type bu_code supplier_number supplier_type item_number item_quantity
ORG 123 456 SUP ITM123 5
ORG 123 456 SUP ITM456 10
ORG 123 456 SUP ITM789 1
ORG 456 789 SUP ITM23 1
ORG 456 789 SUP ITM10 7

The requirement was simple, a JSON payload is set to an API, the service then transforms the data into the DB format and then inserts the data into the tables.

Simple, easy job, let’s use SpringBoot and JPA to create the API and corresponding logic.

The special part about this requirement was that every payload and record sent to the API was guaranteed to be unique, meaning that I don’t need to do an upsert, I can confidently execute an insert statement.

First Attempt:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;

import org.hibernate.validator.constraints.Length;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Table(name = "PARENT_TABLE")
@Getter @Setter @ToString @NoArgsConstructor @EqualsAndHashCode @AllArgsConstructor @Builder // Lombok Annotations
@IdClass(SupplierItemIdentifierId.class)
public class SupplierItemIdentifier {

	@Id
	@Column(name = "BU_TYPE", nullable = false)
	@Length(max = 3)
	private String buType;

	@Id
	@Column(name = "BU_CODE", nullable = false)
	@Length(max = 10)
	private String buCode;

	@Id
	@Column(name = "SUPPLIER_NUMBER", nullable = false)
	@Length(max = 10)
	private String supplierNumberString;

	@Id
	@Column(name = "SUPPLIER_TYPE", nullable = false)
	@Length(max = 3)
	private String supplierType;
	
	@Column(name = "ADDITIONAL_FIELD_1", nullable = false)
	@Length(max = 12)
	private String additionalField1;
	
}

The IdClass annotation was used to handle the Composite Key (the first 4 fields).

After implementing the above, I started performing some tests and noticed that the JPA engine was first querying the database to check does a record exist for this particular ID combination, as stated above the special part about this use-case was that the data passed into the API was guaranteed to be unique every time.

Due to this, I decided to change the above Model to force the JPA engine to do an insert into the database without the first corresponding select statement, but to implement the forced insert I didn’t want to manage the transaction or explicitly do an insert, I wanted to keep using JPA with minimal changes, which led me to the Persistable class.

When the JPA Repository executes the save method, it firstly checks whether the entity isNew and if true then it fires a persist call from the entity manager as opposed to a merge call.

What constitutes isNew to return true is based on a few factors, the first and most common is that if your model has a field annotated with Id and if that ID field is null then the record can be classified as new - This is useful if Spring is managing or generating the ID fields but that doesn’t apply in this case.

The second way to control the outcome of isNew is for the entity to implement Persistable which then calls the isNew method on the entity, this was a solution to the earlier issue.

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;

import org.hibernate.validator.constraints.Length;
import org.springframework.data.domain.Persistable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Table(name = "PARENT_TABLE")
@Getter @Setter @ToString @NoArgsConstructor @EqualsAndHashCode @AllArgsConstructor @Builder // Lombok Annotations
@IdClass(SupplierItemIdentifierId.class)
public class SupplierItemIdentifier implements Persistable<SupplierItemIdentifierId> {

	@Id
	@Column(name = "BU_TYPE", nullable = false)
	@Length(max = 3)
	private String buType;

	@Id
	@Column(name = "BU_CODE", nullable = false)
	@Length(max = 10)
	private String buCode;

	@Id
	@Column(name = "SUPPLIER_NUMBER", nullable = false)
	@Length(max = 10)
	private String supplierNumberString;

	@Id
	@Column(name = "SUPPLIER_TYPE", nullable = false)
	@Length(max = 3)
	private String supplierType;
	
	@Column(name = "ADDITIONAL_FIELD_1", nullable = false)
	@Length(max = 12)
	private String additionalField1;

	// Added the below to prevent JPA from doing a SELECT+INSERT as the data is always unique
	@Override
	public SupplierItemIdentifierId getId() {
		return SupplierItemIdentifierId.builder()
			.buType(buType)
			.buCode(buCode)
			.supplierNumberString(supplierNumberString)
			.supplierType(supplierType)
			.build();
	}

	@Override
	public boolean isNew() {
		return true;
	}
	
}

As you can see, the isNew method always returns true which is exactly what our use-case allowed, however, you could include more complex logic to determine if a record was “new” or not in this method.

After the above change, when the entity manager determines if the entity isNew, it executes the isNew method on the entity itself which on our case returns true therefore meaning the data is inserted without the prior select statement being executed.

Simple, yet effective.

SupplierItemIdentifierId Class

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter @Setter @ToString @NoArgsConstructor @EqualsAndHashCode @AllArgsConstructor @Builder
public class SupplierItemIdentifierId implements Serializable {

	private static final long serialVersionUID = 1345785245214L;

	private String buType;
	private String buCode;
	private String supplierNumberString;
	private String supplierType;
	
}

TL;DR:- Implementing persistable on a Java entity can force JPA to either always insert or always update the records by overriding the isNew method instead of first checking whether the record exists via a select and then deciding whether to insert or update the record, this can heavily reduce the overall number of SQL statements executed by JPA. This is done automatically if you use a JPA Generated ID but there are times where you can’t control the ID within your Java entities…