Spring - Data JPA Hibernate

Table Of Contents

1. Hibernate

1. Flags you have to set

2. Problems and solutions

1. N+1 problem

2. Cartesian Product

3. Hold database connection too long

4. Too many queries

5. Tips, Tricks and have to know

3. Basic annotation use

4. Relationships

1. Basic info and best practices

5. Inheritance

6. To research

7. Sources

Hibernate

This chapter is about Spring Data JPA with as implementation Hibernate.

Flags you have to set

spring.jpa.open-in-view=false

Let the business layer decide when it should start and stop an transaction (with @transactional or transactionTemplate).

If this is set to true the transaction will be held during the whole rendering phase.

Transaction will be held until something is returned from the request, so if you have an IO intensive request you will hold the transaction and the db connection during that time.

spring.jpa.auto-commit=false

Again let the business layer decide when a entity should be saved.

Saves on db resources.

hibernate.query.fail_on_pagination_over_collection_fetch=true

Let hibernate fail if sorting is happening in memory and not on database level. This can happen when using sorting together with entity graphs.

Problems and solutions

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=false

Anti-pattern!! Do not use. Is normally used when you want to fetch objects outside of the hibernate lifecycle. So you can avoid LazyInitializationException.

N+1 problem

if you lazy fetch a Many relation you will fetch the relation one by one resulting in database performance issues. Solutions:

  • use eager —> never use this! See Cartesian Product for why
  • open-session-in-view—> anti-pattern, can hold transaction for too long. Also let business layer decide when to close the connection.
  • Always try to use lazy for any relationship.
  • @BatchSize —> not really a solution but can usefully in a pinch
  • @FetchMode.Subselect —> not really a solution but can usefully in a pinch
  • use fetch joins or entity graphs (recommended).
    • use data access layer query, JPQL or HQL (fetch joins) —> pagination is difficult here
    • use entity graphs

Cartesian Product

When overriding the default lazy fetch plan for ToMany relations (where a class has a list/set). You fetch everything with left outer join, as (legacy/default) databases do not have array concepts, you fetch a row per relation, resulting in a row per relation item. If have a parent with 4 childeren and these 4 childeren have each 3 childeren on their own you would get 4x3= 12 rows back. This example is small but even with small quantities you can have big results: 100 items x 1000 subitems = 10 000 rows returned. So even with small quantities you begin to overload your db and application.

Try to avoid the cartesian Product and use Lazy loading for fetching multiple relations! You can also get MutipleBagFetchException from eager loading Many relations, use a set or ordered list.

Hold database connection too long

  • you should log how long you hold the database connection
  • See that @Transactional is used and not ignored by Spring Boot by setting open-view jpa flag to false.
  • See that @Transactional is not used on methods that are IO intensive. Use TransactionTemplate for that, so you only use the transaction for writing to the database.

Too many queries

  • use optimistic locking by using @Version jpa annotation. So Hibernate does not need to fetch the object from the database to see if it is the latest version before inserting. Or use Persistable interface. source: persistable only entities
  • If you only need to use the ids of the objects and not the whole object you can use getRefById, which only fetches the id of the object. Useful for deleting objects, …
  • use home rolled queries: fetch join or entity graphs.
  • use dynamicUpdate on entities: it ensures that Hibernate uses only the modified columns in the SQL statement that it generates for the update of an entity. source: dynamic updates

Tips, Tricks and have to know

  • for modify use entities. For reading use projections (records) with only the columns you need.
  • you can use generic findby’s in the repo’s. <T> findBy.... So you can dynamically choose what you need to fetch by choosing which class you use to fetch.
  • Repo interface comes in different flavors, last item builds further on the item above it:
    • Crud
    • PagingAndSorting
    • Jpa (for using jpa specific methods, flush, …)
  • @Transient is used to mark fields which should not be persisted.
  • you can use following levels of fallbacks:
    • Entity graph API
    • JPQL Jakarta Query Language or Hibernate Query language (same but hibernate flavored) (fetch queries)
    • Criteria Query
    • Native SQL queries or JOOQ

Basic annotation use

import jakarta.persistence.*;


// How the entity should be named, 
// you can use this in 
// Jakarta Persistence Query Language or Hibernate Query Language
@Entity("note")
// On which database table the entity should map
@Table("note_v2") 
public class Note {
  @Id
  @GenerateValue(strategy = GenerationType.UUID)
  private UUID uuid;

  /**
  * Optional, can be used on columns that are directly mapped. 
  * changes on annotation are only applicable to the variable not the database column.
  * Has 2 attributes, optional (should allow null) and fetch (fetchType Eager or Lazy)
  * Lazy loading can be used for serializable objects which can be big --> blob like formate for images
  */
  @Basic(optional = false, fetch = FetchType.LAZY)
  /**
   * Column can be used in conjunction with other annotations (basic, ...)
   * Is used for mapping on database columns
   * 
   */
  @Column(name = "name_v2")
  private String name;

  @CreationTimestamp
  @Temporal(TemporalType.TIMESTAMP)
  @Column(name = "date_created")
  private ZonedDateTime dateCreated;

  @UpdateTimestamp
  /**
   * Mapped on Date or Time objects, is used to specify how the time data is stored
   * java.sql. --> Timestamp, Time Date
   */
  @Temporal(TemporalType.TIMESTAMP)
  @Column(name = "date_updated")
  private ZonedDateTime dateUpdated;

  /**
  * The @Lob annotation specifies that the database should store the property as Large Object. 
  * The columnDefinition in the @Column annotation defines the column type for the property.
  */
  @Lob
  @Column(name = "photo", columnDefinition="BLOB")
  private byte[] photo;

}

Relationships

Basic info and best practices

HibernateType Description
Bag Can contain duplicates and no order
List Can contain duplicates but with order reserved. @OrderBy on list.
Set No duplicates and no order
JavaListType HibernateListType Description Performance
List Bag Allows duplicate elements in it - unordered. Bad needs an extra column for ordering, after deleting reordering is required
Ordered List List Allows duplicate elements in it - is ordered Bad needs an extra column for ordering, after deleting reordering is required
Set Set All elements should be unique. Good does not need reordering after deleting of element

sources:

https://stackoverflow.com/questions/17543027/what-to-choose-between-set-and-list-in-hibernate

Always define FetchType to Lazy!!

Relation DefaultFetchType
@ManyToOne Eager
@OneToOne Eager
@OneToMany Lazy
@ManyToMany Lazy
  • always try to use a bidirectional mapping
  • Always try to use a set when possible, you can even order sets (@orderBy)
  • use a JoinColumn on the many side
  • @ManyToOne should control the associations and @OneToMany should be using @MappedBy
  • always FetchType LAZY
  • Don’t use cascade type REMOVE on lists/sets, will be removed one by one
  • use orphanRemoval when applicable
  • add helper methods to update the bidirectional relation
  • avoid mapping of huge to-many associations, use pagination when possible
@Table("parent")
@Entity("parent")
public class Parent {
  @OneToMany
  @JoinColumn(name = "fk_child")
  private Set<Child> children = new HashSet<Child>();

  // helper method
  public void AddChild(Child child) {
    this.children.add(child);
    child.setParent(this);
  }

}

public class Child {
  @ManyToOne
  private Parent parent;
}

Inheritance

  • single table inheritance
  • class table inheritance / joined
  • concrete table inheritance / table per class
  • @mapped super class (without being mirrored into the database)

To research

  • cascadeTypes?, orphanRemoval?, inverseJoinColumns, fetchModes

  • optimistic locking vs pessimistic locking

  • have a look at tooling: flexypool, digma, QuickPerf

  • How JOOQ fits with JPA.

  • @embeddable, @ElementCollection, @collectiontable,@optimisticLocking

  • ANSI SQL isolation levels

    • dirty, unrepeatable, phantom reads
  • best practices relations

    • check cascade types best practices
  • How to map inheritance —> multiple solutions

Sources