Building Fast and Scalable Persistence Layers with Spring Data JPA



Thorben Janssen as a Freelancer, Self-employed






English 🇺🇸


Intermediate/Advanced Spring



  • ✅ Practical workshop with an on-spot set of tips for working with Hibernate and Spring Data JPA effectively.

  • ⛔ An alternative to SpEL was introduced, but not to the nested association within advanced DTO projection.

"Favour DTO interfaces projections without advanced techniques (SpEL, nested associations) for read-operations over managed entities."

Hibernate statistics


  • hibernate.generate_statistics=true


  • org.hibernate.stats=DEBUG logging for DEV purposes

Association management


N-to-many relationships: Stick to the default mapping FetchType.LAZY and use fetching for specific queries if required.

N-to-one relationships: Check existing mappings individually and use FetchType.LAZY for new ones.

N+1 problem

It is a problem that lazy fetching introduces.

Fetch all required entities with one query using Fetch Joins (JOIN FETCH in @Query or Entity Graphs (@NamedEntityGraph` and @EntityGraph)

Beware of left-joining as the complexity of the queue raises and it becomes slower. Although it’s recommended to not join more than one association, it also depends on whether it contains 3 or thousands of rows.


Hibernate handles List inefficiently (it removes all associations and adds remaining ones) so is better to use Set.


Projections help to fine-tune the data that you need, so less data is selected from the database


Entities are managed by the current persistence context, which means at the end of the transaction the managed entity is updated in the database through queries


They don’t need lifecycle management overhead (no INSERT/UPDATE/DELETE).

Scalar values are object arrays (Obejct[]) and need to remember the order in the SELECT clause.

DTO classes (without @Entity for pure JPA) and interfaces with matching getter methods (easier, Spring Data JPA generates a class based on the definition) are preferred over scalar values.

Advanced DTO projections pitfalls

Nested associations such as a getter returning List<ClientDtoProjecion> on an interface-based DTO projection defies all the benefits of the DTO projections introduced earlier.

SpEL such as returning first and last name together using @Value("#{target.lastName + ', ' + target.firstName}") over a getter that actually selects a whole entity and it is better to use a default interface method performing the concatenation instead and preserve the benefits of DTO projections.


1st level cache

Each Hibernate session has 1st level cache assuring we have only one object representation of each record in the database within each session, but it is useless performance-wise when each user has their session.

2nd level cache

It is a session-independent entity store containing entity objects and is used whenever an entity is found by its primary key (or if associations are traversed), but it creates some additional problems because now this external cache is needed to keep in sync (check first) that creates a small overhead, and it’s needed to compensate with a higher number of hits (rule of thumb is at least 9-10 reads per 1 write operation).

It needs to be activated in properties with a specific cache mode where ENABLE_SELECTIVE and DISABLE_SELECTIVE is recommended over ALL/NONE/UNSPECIFIED and managed through @Cacheable annotation on the repository class/method or entity class level.

Place @Cache(use = CacheCocurrencyStrategy.TRANSACTIONAL) over many-to-many associations and all associations on entities that don’t map the foreign key column, because in these cases Hibernate doesn’t cache the association between these two objects but only the objects themselves.