Thursday, March 13, 2014

Hibernate optimizations beyond JPA

Optimizations on the persistence layer not supported by JPA

With the JPA 2 standard a great support came for storing and referencing data in collections. 
Entities can now reference other objects that are of the type basic, used for primitive type.
The annotation for supporting this is @ElementCollection.
With this a separate table is created used to store the primitive types. 
It can be specified further with @CollectionTable and @Column.

For small amount of types you would define the class of the entity containing the primitive type as @Embeddable. 

If the size of the primitive types is big/very big, you would leave the default behavior and let the persistence provider to lazy load the primitives types as they are needed.

More thoughts have to put into a real relationship between entities.
If you don't pay attention you could very easily run into the famous N+1-SELECT-problem.
If an entity has a one-to-many relationship to another entity and no specific loading strategy is defined. The standard lazy-loading will lead to the fact, that on every access to an attribute of the other entity, this entity will be loaded separately. 
Common use case:
1) N Entities are loaded from a persistence service
2) Looping over this result(N) set and applying certain business logic on it
3) With this the entity graph is used and the code navigates to the one-to-many relationship 
4) With the lazy-loading configuration of this relationship for every item in the loop another fetch of the one-to-many relationship is done. The persistence provider will generate an additional SELECT.

What to do in order to optimize this situation?
  • usage of @BatchSize  (non-JPA compliant)
  • usage of SUBSELECT-FETCH  (non-JPA compliant)
  • own criteria/JPQL for joining both entities
  • ...
But with the first 2 points we are already outside of the JPA standard.
@BatchSize could ease the problem that the fetching is reduced from n+1 to a n+batchSize+1 problem.

@Entity 
public class A

@ManyToOne
@BatchSize(size=5)
public B getBs() {
  ..
}

The second point leads to completely load the collection after entity A is loaded.
@Entity 
public class A

@ManyToOne
@Fetch(FetchMode.SUBSELECT)
public B getBs() {
  ..
}

But this is again a Hibernate dependency and Hibernate supports this on collections, and all XToMany-relationships. 

Leaving it to 3. point and the danger of retrieving too much data.