- Notifications
You must be signed in to change notification settings - Fork538
Collection of 300+ best practices for Java persistence performance in Spring Boot applications
License
AnghelLeonard/Hibernate-SpringBoot
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Hibernate & Spring Boot Samples
Description: This application is a sample of how to store date, time, and timestamps in UTC time zone. The second setting,useLegacyDatetimeCode
is needed only for MySQL. Otherwise, set onlyhibernate.jdbc.time_zone
.
Key points:
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
spring.datasource.url=jdbc:mysql://localhost:3306/screenshotdb?useLegacyDatetimeCode=false
Description: View the prepared statement binding/extracted parameters via Log4J 2 logger setting.
Key points:
- for Maven, in
pom.xml
, exclude Spring Boot's Default Logging - for Maven, in
pom.xml
, Add Log4j 2 Dependency - in
log4j2.xml
add,<Logger name="org.hibernate.type.descriptor.sql" level="trace"/>
Description: View the query details (query type, binding parameters, batch size, execution time, etc) viaDataSource-Proxy
Key points:
- for Maven, add in
pom.xml
thedatasource-proxy
dependency - create an bean post processor to intercept the
DataSource
bean - wrap the
DataSource
bean viaProxyFactory
and an implementation ofMethodInterceptor
Description: Batch inserts viaSimpleJpaRepository#saveAll(Iterable<S> entities)
method in MySQL
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since MySQL
IDENTITY
will cause insert batching to be disabled - in entity, add
@Version
property to avoid extra-SELECT
statements fired before batching (also prevent lost updates in multi-request transactions). Extra-SELECT
statements are the effect of usingmerge()
instead ofpersist()
; behind the scene,saveAll()
usessave()
, which in case of non-new entities (entities that have IDs) will callmerge()
, which instruct Hibernate to fire aSELECT
statement to make sure that there is no record in the database having the same identifier - pay attention on the amount of inserts passed to
saveAll()
to not "overwhelm" the Persistence Context; normally theEntityManager
should be flushed and cleared from time to time, but during thesaveAll()
execution you simply cannot do that, so if insaveAll()
there is a list with a high amount of data, all that data will hit the Persistence Context (1st Level Cache) and will remain in memory until the flush time; using relatively small amount of data should be ok (in this example, each batch of 30 entities run in a separate transaction and Persistent Context) - the
saveAll()
method return aList<S>
containing the persisted entities; each persisted entity is added into this list; if you just don't need thisList
then it is created for nothing - if is not needed, then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: This application is a sample of batching inserts viaEntityManager
in MySQL. This way you can easily control theflush()
andclear()
cycles of the Persistence Context (1st Level Cache) inside the current transaction. This is not possible via Spring Boot,saveAll(Iterable<S> entities)
, since this method executes a single flush per transaction. Another advantage is that you can callpersist()
instead ofmerge()
- this is used behind the scene by the SpringBootsaveAll(Iterable<S> entities)
andsave(S entity)
.
If you want to execute a batch per transaction (recommended) then check thisexample.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since MySQL
IDENTITY
will cause insert batching to be disabled - in your DAO layer, flush and clear the Persistence Context from time to time (e.g. for each batch); this way you avoid to "overwhelm" the Persistence Context
- if is not needed, then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: Batch inserts viaJpaContext/EntityManager
in MySQL.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since MySQL
IDENTITY
will cause insert batching to be disabled - the
EntityManager
is obtain per entity type via,JpaContext#getEntityManagerByManagedType(Class<?> entity)
- in DAO, flush and clear the Persistence Context from time to time; this way you avoid to "overwhelm" the Persistence Context
- if is not needed, then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: Batch inserts via Hibernate session-level batching (Hibernate 5.2 or higher) in MySQL.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since MySQL
IDENTITY
will cause insert batching to be disabled - the Hibernate
Session
is obtained by un-wrapping it viaEntityManager#unwrap(Session.class)
- the batching size is set via
Session#setJdbcBatchSize(Integer size)
and get viaSession#getJdbcBatchSize()
- in DAO, flush and clear the Persistence Context from time to time; this way you avoid to "overwhelm" the Persistence Context
- if is not needed, then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: Direct fetching via Spring Data,EntityManager
and HibernateSession
examples.
Key points:
- direct fetching via Spring Data uses
findById()
- direct fetching via JPA
EntityManager
usesfind()
- direct fetching via Hibernate
Session
usesget()
Note: You may also like to read the recipe,"How To Enrich DTOs With Virtual Properties Via Spring Projections"
Description: Fetch only the needed data from the database via Spring Data Projections (DTO).
Key points:
- write an interface (projection) containing getters only for the columns that should be fetched from the database
- write the proper query returning a
List<projection>
- if it is applicable, limit the number of returned rows (e.g., via
LIMIT
) - in this example, we can use query builder mechanism built into Spring Data repository infrastructure
Note: Using projections is not limited to use query builder mechanism built into Spring Data repository infrastructure. We can fetch projections via JPQL or native queries as well. For example, in thisapplication we use a JPQL.
Output example (select first 2 rows; select only "name" and "age"):
Description: By default, the attributes of an entity are loaded eagerly (all at once). But, we can load themlazy as well. This is useful for column types that store large amounts of data:CLOB
,BLOB
,VARBINARY
, etc ordetails that should be loaded on demand. In this application, we have an entity namedAuthor
. Its properties are:id
,name
,genre
,avatar
andage
. And, we want to load theavatar
lazy. So, theavatar
should be loaded on demand.
Key points:
- in
pom.xml
, activate Hibernatebytecode enhancement (e.g. use Mavenbytecode enhancement plugin) - in entity, annotate the attributes that should be loaded lazy with
@Basic(fetch = FetchType.LAZY)
- in
application.properties
, disable Open Session in View
Check as well:
-Default Values For Lazy Loaded Attributes
-Attribute Lazy Loading And Jackson Serialization
Description: A Hibernate proxy can be useful when a child entity can be persisted with a reference to its parent (@ManyToOne
or@OneToOne
association). In such cases, fetching the parent entity from the database (execute theSELECT
statement) is a performance penalty and a pointless action, because Hibernate can set the underlying foreign key value for an uninitialized proxy.
Key points:
- rely on
EntityManager#getReference()
- in Spring, use
JpaRepository#getOne()
-> used in this example - in Hibernate, use
load()
- assume two entities,
Author
andBook
, involved in a unidirectional@ManyToOne
association (Author
is the parent-side) - we fetch the author via a proxy (this will not trigger a
SELECT
), we create a new book, we set the proxy as the author for this book and we save the book (this will trigger anINSERT
in thebook
table)
Output example:
- the console output will reveal that only an
INSERT
is triggered, and noSELECT
Description: The N+1 is an issue of lazy fetching (but, eager is not exempt). This application reproduce the N+1 behavior.
Key points:
- define two entities,
Author
andBook
in a lazy bidirectional@OneToMany
association - fetch all
Book
lazy, so withoutAuthor
(results in 1 query) - loop the fetched
Book
collection and for each entry fetch the correspondingAuthor
(results N queries) - or, fetch all
Author
lazy, so withoutBook
(results in 1 query) - loop the fetched
Author
collection and for each entry fetch the correspondingBook
(results N queries)
Description: Starting with Hibernate 5.2.2, we can optimize JPQL (HQL) query entites of typeSELECT DISTINCT
viaHINT_PASS_DISTINCT_THROUGH
hint. Keep in mind that this hint is useful only for JPQL (HQL) JOIN FETCH-ing queries. Is not useful for scalar queries (e.g.,List<Integer>
), DTO orHHH-13280. In such cases, theDISTINCT
JPQL keyword is needed to be passed to the underlying SQL query. This will instruct the database to remove duplicates from the result set.
Key points:
- use
@QueryHints(value = @QueryHint(name = HINT_PASS_DISTINCT_THROUGH, value = "false"))
Note: The HibernateDirty Checking mechanism is responsible to identify the entitites modifications at flush-time and to trigger the correspondingUPDATE
statements in our behalf.
Description: Prior to Hibernate version 5, theDirty Checking mechanism relies on Java Reflection API for checking every property of every managed entity. Starting with Hibernate version 5, theDirty Checking mechanism can rely on theDirty Tracking mechanism (which is the capability of an entity to track its own attributes changes) which requires HibernateBytecode Enhancement to be present in the application. TheDirty Tracking mechanism sustain a better performance, especially when you have a relatively large number of entitites.
ForDirty Tracking, duringBytecode Enhancement process, the entity classes bytecode is instrumented by Hibernate by adding atracker,$$_hibernate_tracker
. At flush time, Hibernate will use thistracker to discover the entities changes (each entitytracker will report the changes). This is better than checking every property of every managed entity.
Commonly (by default), the instrumentation takes place at build-time, but it can be configured to take place at runtime or deploy-time as well. It is preferable to take place at build-time for avoiding an overhead in the runtime.
AddingBytecode Enhancement and enablingDirty Tracking can be done via a plugin added via Maven or Gradle (Ant can be used as well). We use Maven, therefore we add it inpom.xml
.
Key points:
- Hibernate come withBytecode Enhancement plugins for Maven, Gradle (Ant can be used as well)
- for Maven, add theBytecode Enhancement plugin in the
pom.xml
file
TheBytecode Enhancement effect can be seen onAuthor.class
here. Notice how the bytecode was instrumented with$$_hibernate_tracker
.
Description: This application is an example of how is correct to use the Java 8Optional
in entities and queries.
Key points:
- use the Spring Data built-in query-methods that return
Optional
(e.g.,findById()
) - write your own queries that return
Optional
- use
Optional
in entities getters - in order to run different scenarios check the file,
data-mysql.sql
Description: This application is a proof of concept of how is correct to implement the bidirectional@OneToMany
association from the performance perspective.
Key points:
- always cascade from parent to child
- use
mappedBy
on the parent - use
orphanRemoval
on parent in order to remove children without references - use helper methods on parent to keep both sides of the association in sync
- use lazy fetching on both side of the association
- as entities identifiers, use assigned identifiers (business key, natural key (
@NaturalId
)) and/or database-generated identifiers and override (on child-side) properly theequals()
andhashCode()
methods ashere - if
toString()
need to be overridden, then pay attention to involve only the basic attributes fetched when the entity is loaded from the database
Note: Pay attention to remove operations, especially to removing child entities. TheCascadeType.REMOVE
andorphanRemoval=true
may produce too many queries. In such scenarios, relying onbulk operations is most of the time the best way to go for deletions.
Description: This application is an example of how to write a query viaJpaRepository
,EntityManager
andSession
.
Key points:
- for
JpaRepository
use@Query
or Spring Data Query Creation - for
EntityManager
andSession
use thecreateQuery()
method
Description: In MySQL & Hibernate 5, theGenerationType.AUTO
generator type will result in using theTABLE
generator. This adds a significant performance penalty. Turning this behavior toIDENTITY
generator can be obtained by usingGenerationType.IDENTITY
or thenative generator.
Key points:
- use
GenerationType.IDENTITY
instead ofGenerationType.AUTO
- use thenative generator - exemplified in this application
Description: This application is an example when callingsave()
for an entity is redundant (not necessary).
Key points:
- at flush time, Hibernate relies ondirty checking mechanism to determine the potential modifications in entities
- for each modification, Hibernate automatically triggers the corresponding
UPDATE
statement without the need to explicitly call thesave()
method - behind the scene, this redundancy (calling
save()
when is not necessarily) doesn't affect the number of triggered queries, but it implies a performance penalty in the underlying Hibernate processes
Description: In PostgreSQL, usingGenerationType.IDENTITY
will disable insert batching. The(BIG)SERIAL
is acting "almost" like MySQL,AUTO_INCREMENT
. In this application, we use theGenerationType.SEQUENCE
which permits insert batching, and we optimize it via thehi/lo
optimization algorithm.
Key points:
- use
GenerationType.SEQUENCE
instead ofGenerationType.IDENTITY
- rely on the
hi/lo
algorithm to fetch ahi value in a database roundtrip (thehi value is useful for generating a certain/given number of identifiers in-memory; until you haven't exhausted all in-memory identifiers there is no need to fetch anotherhi) - you can go even further and use the Hibernate
pooled
andpooled-lo
identifier generators (these are optimizations ofhi/lo
that allows external services to use the database without causing duplication keys errors) - optimize batching via
spring.datasource.hikari.data-source-properties.reWriteBatchedInserts=true
Description: This application is a sample of using JPA Single Table inheritance strategy (SINGLE_TABLE
).
Key points:
- this is the default inheritance strategy (
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
) - all the classes in an inheritance hierarchy are represented via a single table in the database
- subclasses attributes non-nullability is ensured via
@NotNull
and MySQL triggers - the default discriminator column memory footprint was optimized by declaring it of type
TINYINT
Output example (below is a single table obtained from 3 entities):
Description: This application is a sample of counting and asserting SQL statements triggered "behind the scene". Is very useful to count the SQL statements in order to ensure that your code is not generating more SQL statements that you may think (e.g., N+1 can be easily detected by asserting the number of expected statements).
Key points:
- for Maven, in
pom.xml
, add dependencies for DataSource-Proxy library and Vlad Mihalcea's db-util library - create the
ProxyDataSourceBuilder
withcountQuery()
- reset the counter via
SQLStatementCountValidator.reset()
- assert
INSERT
,UPDATE
,DELETE
andSELECT
viaassertInsert/Update/Delete/Select/Count(long expectedNumberOfSql)
Output example (when the number of expected SQLs is not equal with the reality an exception is thrown):
Description: This application is a sample of setting the JPA callbacks (Pre/PostPersist
,Pre/PostUpdate
,Pre/PostRemove
andPostLoad
).
Key points:
- in entity, write callback methods and use the proper annotations
- callback methods annotated on the bean class must return
void
and take no arguments
Description: Instead ofregular unidirectional/bidirectional@OneToOne
better rely on an unidirectional@OneToOne
and@MapsId
. This application is a proof of concept.
Key points:
- use
@MapsId
on child side - use
@JoinColumn
to customize the name of the primary key column - mainly, for
@OneToOne
associations,@MapsId
will share the primary key with the parent table (id
property acts as both primary key and foreign key)
Note:
@MapsId
can be used for@ManyToOne
as well
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely onSqlResultSetMapping
andEntityManager
.
Key points:
- use
SqlResultSetMapping
andEntityManager
- for using Spring Data Projections check thisitem
Note: If you want to rely on the{EntityName}.{RepositoryMethodName}
naming convention for simply creating in the repository interface methods with the same name as of native named query then skip this application andcheck this one.
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely onSqlResultSetMapping
,NamedNativeQuery
.
Key points:
- use
SqlResultSetMapping
,NamedNativeQuery
- for using Spring Data Projections check thisitem
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely onjavax.persistence.Tuple
and native SQL.
Key points:
- use
java.persistence.Tuple
in a Spring repository and mark the query asnativeQuery = true
- for using Spring Data Projections check thisitem
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely onjavax.persistence.Tuple
and JPQL.
Key points:
- use
java.persistence.Tuple
in a Spring repository - for using Spring Data Projections check thisitem
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely on Constructor Expression and JPQL.
Key points:
- write a proper constructor in the DTO class
- use a query as
SELECT new com.bookstore.dto.AuthorDto(a.name, a.age) FROM Author a
- for using Spring Data Projections check thisitem
See also:
How To Fetch DTO Via Constructor And Spring Data Query Builder Mechanism
Description: Fetching more data than needed is prone to performance penalties. Using DTO allows us to extract only the needed data. In this application we rely on Hibernate,ResultTransformer
and native SQL.
Key points:
- use
AliasToBeanConstructorResultTransformer
for DTO without setters, but with constructor - use
Transformers.aliasToBean()
for DTO with setters - use
EntityManager.createNativeQuery()
andunwrap(org.hibernate.query.NativeQuery.class)
- starting with Hibernate 5.2,
ResultTransformer
is deprecated, but until a replacement will be available (probably in Hibernate 6.0) it can be used (read further) - for using Spring Data Projections check thisrecipe
Description: Fetching more data than needed is prone to performance penalties. Using DTO allows us to extract only the needed data. In this application we rely on Hibernate,ResultTransformer
and JPQL.
Key points:
- use
AliasToBeanConstructorResultTransformer
for DTO without setters, with constructor - use
Transformers.aliasToBean()
for DTO with setters - use
EntityManager.createQuery()
andunwrap(org.hibernate.query.Query.class)
- starting with Hibernate 5.2,
ResultTransformer
is deprecated, but until a replacement will be available (in Hibernate 6.0) it can be used (read further) - for using Spring Data Projections check thisitem
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely onBlaze-Persistence entity views.
Key points:
- for Maven, add in
pom.xml
the dependencies specific to Blaze-Persistence - configure Blaze-Persistence via
CriteriaBuilderFactory
andEntityViewManager
- write anentity view via an interface in Blaze-Persistence fashion
- write a Spring-centric repository by extending
EntityViewRepository
- call method of this repository such as,
findAll()
,findOne()
, etc - for using Spring Data Projections check thisitem
Description: This application reveals the possible performance penalties of using@ElementCollection
. In this case, without@OrderColumn
. As you can see in the next item (34) adding@OrderColumn
can mitigate some performance penalties.
Key points:
- an
@ElementCollection
doesn't have a primary key - an
@ElementCollection
is mapped in a separate table - avoid
@ElementCollection
when you have a lot of inserts/deletes on this collection; inserts/deletes will cause Hibernate to delete all the existing table rows, process the collection in-memory, and re-insert the remaining table rows to mirror the collection from memory - the more entries we have in this collection the greater the performance penalty will be
Description: This application reveals the performance penalties of using@ElementCollection
. In this case, with@OrderColumn
. But, as you can see in this application (in comparison with item 33), by adding@OrderColumn
can mitigate some performance penalties when operations takes place near the collection tail (e.g., add/remove at/from the end of the collection). Mainly, all elements situated before the adding/removing entry are left untouched, so the performance penalty can be ignored if we affect rows close to the collection tail.
Key points:
- an
@ElementCollection
doesn't have a primary key - an
@ElementCollection
is mapped in a separate table - prefer
@ElementCollection
with@OrderColumn
when you have a lot of inserts and deletes near the collection tail - the more elements are inserted/removed from the beginning of the collection the greater the performance penalty will be
Note: Before reading this item try to see ifHibernate5Module is not what you are looking for.
Description: The Open-Session in View anti-pattern is activated by default in SpringBoot. Now, imagine a lazy association (e.g.,@OneToMany
) between two entities,Author
andBook
(an author has associated more books). Next, a REST controller endpoint fetches anAuthor
without the associatedBook
. But, the View (more precisely, Jackson), forces the lazy loading of the associatedBook
as well. Since OSIV will supply the already openedSession
, the proxies initializations take place successfully. The solution to avoid this performance penalty starts by disabling the OSIV. Further, explicitly initialize the un-fetched lazy associations. This way, the View will not force lazy loading.
Key points:
- disable OSIV by adding in
application.properties
this setting:spring.jpa.open-in-view=false
- fetch an
Author
entity and initialize its associatedBook
explicitly with (default) values (e.g.,null
) - set
@JsonInclude(Include.NON_EMPTY)
on this entity-level to avoid renderingnull
or what is considered empty in the resulted JSON
NOTE: If OSIV is enabled, the developer can still initialize the un-fetched lazy associations manually as long as he does this outside of a transaction to avoid flushing. But, why is this working? Since theSession
is open, why the manually initialization of the associations of a managed entity doesn't trigger the flush? The answer can be found in the documentation ofOpenSessionInViewFilter
which specifies that:This filter will by default not flush the HibernateSession
, with the flush mode set toFlushMode.NEVER
. It assumes to be used in combination with service layer transactions that care for the flushing: The active transaction manager will temporarily change the flush mode toFlushMode.AUTO
during a read-write transaction, with the flush mode reset toFlushMode.NEVER
at the end of each transaction. If you intend to use this filter without transactions, consider changing the default flush mode (through the "flushMode" property).
Description: This application is a proof of concept for using Spring Projections(DTO) and inner joins written via JPQL and native SQL (for MySQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a (lazy) bidirectional@OneToMany
association) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write inner joins queries using JPQL/SQL
Description: This application is a proof of concept for using Spring Projections(DTO) and left joins written via JPQL and native SQL (for MySQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a (lazy) bidirectional@OneToMany
association) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write left joins queries using JPQL/SQL
Description: This application is a proof of concept for using Spring Projections(DTO) and right joins written via JPQL and native SQL (for MySQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a (lazy) bidirectional@OneToMany
association) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write right joins queries using JPQL/SQL
Description: This application is a proof of concept for using Spring Projections(DTO) and inclusive full joins written via JPQL and native SQL (for PostgreSQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a (lazy) bidirectional@OneToMany
association) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write inclusive full joins queries using JPQL/SQL
Description: This application is a proof of concept for using Spring Projections(DTO) and exclusive left joins written via JPQL and native SQL (for MySQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a (lazy) bidirectional@OneToMany
association) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write exclusive left joins queries using JPQL/SQL
Description: This application is a proof of concept for using Spring Projections(DTO) and exclusive right joins written via JPQL and native SQL (for MySQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a (lazy) bidirectional@OneToMany
association) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write exclusive right joins queries using JPQL/SQL
Description: This application is a proof of concept for using Spring Projections(DTO) and exclusive full joins written via JPQL and native SQL (for PostgreSQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a (lazy) bidirectional@OneToMany
association) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write exclusive full joins queries using JPQL/SQL
Description: This application is a proof of concept for using Spring post-commit hooks and how they may affect the persistence layer performance.
Key points:
- avoid time-consuming tasks in Spring post-commit hooks since the database connection will remain open until this code finshes
Description: This application is a proof of concept for using Spring Projections (DTO) and join unrelated entities. Hibernate 5.1 introduced explicit joins on unrelated entities and the syntax and behaviour are similar to SQLJOIN
statements.
Key points:
- define serveral entities (e.g.,
Author
andBook
unrelated entities) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g.,
BookstoreDto
) - write joins queries using JPQL/SQL (e.g., queries all authors names and book titles of the given price)
- Why To Avoid Lombok
@EqualsAndHashCode
And@Data
In Entities And How To Overrideequals()
AndhashCode()
Description: Entities should implementequals()
andhashCode()
ashere. The main idea is that Hibernate requires that an entity is equal to itself across all its state transitions (transient,attached,detached andremoved). Using Lombok@EqualsAndHashCode
(or@Data
) will not respect this requirment.
Key points:
AVOID THESE APPROACHES
- Using Lombok default behavior of
@EqualsAndHashCode
(entity:LombokDefaultBook
, test:LombokDefaultEqualsAndHashCodeTest
) - Using Lombok
@EqualsAndHashCode
with primary key only(entity:LombokIdBook
, test:LombokEqualsAndHashCodeWithIdOnlyTest
) - Rely on default
equals()
andhashCode()
(entity:DefaultBook
, test:DefaultEqualsAndHashCodeTest
) - Rely on default
equals()
andhashCode()
containing only the database-generated identifier(entity:IdBook
, test:IdEqualsAndHashCodeTest
)
PREFER THESE APPROACHES
- Rely on business key (entity:
BusinessKeyBook
, test:BusinessKeyEqualsAndHashCodeTest
) - Rely on
@NaturalId
(entity:NaturalIdBook
, test:NaturalIdEqualsAndHashCodeTest
) - Rely on manually assigned identifiers (entity:
IdManBook
, test:IdManEqualsAndHashCodeTest
) - Rely on database-generated identifiers (entity:
IdGenBook
, test:IdGenEqualsAndHashCodeTest
)
See also:
Description: Typically, when we get aLazyInitializationException
we tend to modify the association fetching type fromLAZY
toEAGER
. That is very bad! This is acode smell. Best way to avoid this exception is to rely onJOIN FETCH
(if you plan to modify the fetched entities) orJOIN
+ DTO (if the fetched data is only read).JOIN FETCH
allows associations to be initialized along with their parent objects using a singleSELECT
. This is particularly useful for fetching associated collections.
This application is aJOIN FETCH
example for avoidingLazyInitializationException
.
Key points:
- define two related entities (e.g.,
Author
andBook
in a@OneToMany
lazy-bidirectional association) - write a JPQL
JOIN FETCH
to fetch an author including his books - write a JPQL
JOIN FETCH
(orJOIN
) to fetch a book including its author
Description: This is a Spring Boot example based on the followingarticle. Is a functional implementation of the Vlad's example. It is highly recommended to read that article.
Key points:
- remove the existing database rows that are no longer found in the incoming collection
- update the existing database rows which can be found in the incoming collection
- add the rows found in the incoming collection, which cannot be found in the current database snapshot
Description: This is a Spring Boot example that exploits Hibernate 5.2.10 capability of delaying the connection acquisition as needed. By default, inresource-local mode, a database connection is aquried immediately after calling a method annotated with@Transactional
. If this method contains some time-consuming tasks before the first SQL statement then the connection is hold open for nothing. But, Hibernate 5.2.10 allows us to delay the connection acquisition as needed. This example rely on HikariCP as the default connection pool for Spring Boot.
Key points:
- set
spring.datasource.hikari.auto-commit=false
in application.properties - set
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
inapplication.properties
Note: If systems external to your application need to insert rows in your tables then don't rely onhi/lo
algorithm since, in such cases, it may cause errors resulted from generating duplicated identifiers. Rely onpooled
orpooled-lo
algorithms (optimizations ofhi/lo
).
Description: This is a Spring Boot example of using thehi/lo
algorithm for generating 1000 identifiers in 10 database roundtrips for batching 1000 inserts in batches of 30.
Key points:
- use the
SEQUENCE
generator type (e.g., in PostgreSQL) - configure the
hi/lo
algorithm as inAuthor.java
entity
Description: This application is a proof of concept of how it is correct to implement the bidirectional@ManyToMany
association from the performance perspective.
Key points:
- choose an owning and a
mappedBy
side - materialize the relationships collections via
Set
notList
- use helper methods on the owner of the relationship to keep both sides of the association in sync
- on the owner of the relationship use
CascadeType.PERSIST
andCascadeType.MERGE
, but avoidCascadeType.REMOVE/ALL
- on the owner of the relationship set up join table
@ManyToMany
is lazy by default; keep it this way!- as entities identifiers, use assigned identifiers (business key, natural key (
@NaturalId
)) and/or database-generated identifiers and override (on both sides) properly theequals()
andhashCode()
methods ashere - if
toString()
need to be overridden, then pay attention to involve only for the basic attributes fetched when the entity is loaded from the database
Description: This is a Spring Boot example of removing rows in case of a bidirectional@ManyToMany
usingList
, respectivelySet
. The conclusion is thatSet
is much better! This applies to unidirectional as well!
Key points:
- using
Set
is much more efficent thanList
Description: View the query details vialog4jdbc.
Key points:
- for Maven, in
pom.xml
, addlog4jdbc
dependency
Description: View the prepared statement binding/extracted parameters viaTRACE
.
Key points:
- in
application.properties
add:logging.level.org.hibernate.type.descriptor.sql=TRACE
- or, even better (for filtering SQLs capabilities), in a Logback specific configuration file add the proper logger
Description:Hibernate Types is a set of extra types not supported by default in Hibernate Core. One of these types isjava.time.YearMonth
. This is a Spring Boot application that uses Hibernate Type to store thisYearMonth
in a MySQL database as integer or date.
Key points:
- for Maven, add Hibernate Types as a dependency in
pom.xml
- in entity use
@TypeDef
to maptypeClass
todefaultForType
Note: Using SQL functions in theWHERE
part (not in theSELECT
part) of query in JPA 2.1 can be done viafunction()
ashere.
Description: Trying to use SQL functions (standard or defined) in JPQL queries may result in exceptions if Hibernate will not recognize them and cannot parse the JPQL query. For example, the MySQL,concat_ws
function is not recognized by Hibernate. This application is a Spring Boot application based on Hibernate 5.3, that registers theconcat_ws
function viaMetadataBuilderContributor
and inform Hibernate about it via,metadata_builder_contributor
property. This example uses@Query
andEntityManager
as well, so you can see two use cases.
Key points:
- use Hibernate 5.3 (or, to be precisely, 5.2.18) (e.g., use Spring Boot 2.1.0.RELEASE)
- implement
MetadataBuilderContributor
and register theconcat_ws
MySQL function - in
application.properties
, setspring.jpa.properties.hibernate.metadata_builder_contributor
to point out Hibernate toMetadataBuilderContributor
implementation
Description: This application is a sample of logging only slow queries viaDataSource-Proxy. A slow query is a query that has an execution time bigger than a specificed threshold in milliseconds.
Key points:
- for Maven, add in
pom.xml
the DataSource-Proxy dependency - create an bean post processor to intercept the
DataSource
bean - wrap the
DataSource
bean viaProxyFactory
and an implementation ofMethodInterceptor
- choose a threshold in milliseconds
- define a listener and override
afterQuery()
Description: This application fetches data asPage<dto>
via Spring Boot offset pagination. Most of the time, the data that should be paginated isread-only data. Fetching the data into entities should be done only if we plan to modify that data, therefore, fetchingread only data asPage<entity>
is not preferable since it may end up in a significant performance penalty. TheSELECT COUNT
triggered for counting the total number of records is a subquery of the mainSELECT
. Therefore, there will be a single database roundtrip instead of two (typically, there is one query needed for fetching the data and one for counting the total number of records).
Key points:
- create a Spring projection (DTO) to contains getters only for the columns that should be fetched
- write a repository that extends
PagingAndSortingRepository
- fetch data via a JPQL or native query (that includes counting) into a
List<dto>
- use the fetched
List<dto>
and the properPageable
to create aPage<dto>
Description: This application fetches data asList<dto>
via Spring Boot offset pagination. Most of the time, the data that should be paginated isread-only data. Fetching the data into entities should be done only if we plan to modify that data, therefore, fetchingread only data asList<entity>
is not preferable since it may end up in a significant performance penalty. TheSELECT COUNT
triggered for counting the total number of records is a subquery of the mainSELECT
. Therefore, there will be a single database roundtrip instead of two (typically, there is one query needed for fetching the data and one for counting the total number of records).
Key points:
- create a Spring projection (DTO) to contains getters only for the columns that should be fetched
- write a repository that extends
PagingAndSortingRepository
- fetch data via a JPQL or native query (that includes counting) into a
List<dto>
If you use thespring-boot-starter-jdbc
orspring-boot-starter-data-jpa
"starters", you automatically get a dependency to HikariCP
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up HikariCP viaapplication.properties
only. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users. Check the HickariCP report revealing the connection pool status.
Key points:
- in
application.properties
, rely onspring.datasource.hikari.*
to configure HikariCP
If you use thespring-boot-starter-jdbc
orspring-boot-starter-data-jpa
"starters", you automatically get a dependency to HikariCP
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up HikariCP viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users. Check the HickariCP report revealing the connection pool status.
Key points:
- in
application.properties
, configure HikariCP via a custom prefix, e.g.,app.datasource.*
- write a
@Bean
that returns theDataSource
This application is detailed in thisDZone article.
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up BoneCP viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users.
Key points:
- in
pom.xml
add the BoneCP dependency - in
application.properties
, configure BoneCP via a custom prefix, e.g.,app.datasource.*
- write a
@Bean
that returns theDataSource
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up ViburDBCP viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users.
Key points:
- in
pom.xml
add the ViburDBCP dependency - in
application.properties
, configure ViburDBCP via a custom prefix, e.g.,app.datasource.*
- write a
@Bean
that returns theDataSource
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up C3P0 viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users.
Key points:
- in
pom.xml
add the C3P0 dependency - in
application.properties
, configure C3P0 via a custom prefix, e.g.,app.datasource.*
- write a
@Bean
that returns theDataSource
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up DBCP2 viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users.
Key points:
- in
pom.xml
add the DBCP2 dependency - in
application.properties
, configure DBCP2 via a custom prefix, e.g.,app.datasource.*
- write a
@Bean
that returns theDataSource
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up Tomcat viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users.
Key points:
- in
pom.xml
add the Tomcat dependency - in
application.properties
, configure Tomcat via a custom prefix, e.g.,app.datasource.*
- write a
@Bean
that returns theDataSource
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that uses two data sources (two MySQL databases, one namedauthorsdb
and one namedbooksdb
) with two connection pools (each database uses its own HikariCP connection pool with different settings). Based on the above items is pretty easy to configure two connection pools from two different providers as well.
Key points:
- in
application.properties
, configure two HikariCP connection pools via a two custom prefixes, e.g.,app.datasource.ds1
andapp.datasource.ds2
- write a
@Bean
that returns the firstDataSource
and mark it as@Primary
- write another
@Bean
that returns the secondDataSource
- configure two
EntityManagerFactory
and point out the packages to scan for each of them - put the domains and repositories for each
EntityManager
in the right packages
Note: If you want yo provide a Fluent API without altering setters then considerthis item.
Description: This is a sample application that alter the entities setters methods in order to empower a Fluent API.
Key points:
- in entitites, return
this
instead ofvoid
in setters
Note: If you want yo provide a Fluent API by altering setters then considerthis item.
Description: This is a sample application that add in entities additional methods (e.g., forsetName
, we addname
) methods in order to empower a Fluent API.
Key points:
- in entities, add for each setter an additional method that return
this
instead ofvoid
Most probably this is all you want:How To FetchSlice<entity>
/Slice<dto>
ViafetchAll
/fetchAllDto
Some implementations ofSlice<T> findAll()
:
- This is a thin implementation based on a hard-coded SQL:
"SELECT e FROM " + entityClass.getSimpleName() + " e;"
- This is just another minimalist implementation based on
CriteriaBuilder
instead of hard-coded SQL - This is an implementation that allows us to provide a
Sort
, so sorting results is possible - This is an implementation that allows us to provide a
Sort
and a Spring DataSpecification
- This is an implementation that allows us to provide a
Sort
, aLockModeType
, aQueryHints
and a Spring DataSpecification
- This is an implementation that allows us to provide a Spring Data
Pageable
and/orSpecification
by extending theSimpleJpaRepository
from Spring Data. Bascially, this implementation is the only one that returnsPage<T>
instead ofSlice<T>
, but it doesn't trigger the extraSELECT COUNT
since it was eliminated by overriding thePage<T> readPage(...)
method fromSimpleJpaRepository
. The main drawback is that by returing aPage<T>
you don't know if there is a next page or the current one is the last. Nevertheless, there are workarounds to have this as well. In this implementation you cannot setLockModeType
or query hints.
Story: Spring Boot provides anoffset based built-in paging mechanism that returns aPage
orSlice
. Each of these APIs represents a page of data and some metadata. The main difference is thatPage
contains the total number of records, whileSlice
can only tell if there is another page available. ForPage
, Spring Boot provides afindAll()
method capable to take as arguments aPageable
and/or aSpecification
orExample
. In order to create aPage
that contains the total number of records, this method triggers anSELECT COUNT
extra-query next to the query used to fetch the data of the current page. This can be a performance penalty since theSELECT COUNT
query is triggered every time we request a page. In order to avoid this extra-query, Spring Boot provides a more relaxed API, theSlice
API. UsingSlice
instead ofPage
removes the need of this extraSELECT COUNT
query and returns the page (records) and some metadata without the total number of records. So, whileSlice
doesn't know the total number of records, it still can tell if there is another page available after the current one or this is the last page. The problem is thatSlice
work fine for queries containing the SQL,WHERE
clause (including those that uses the query builder mechanism built into Spring Data), but itdoesn't work forfindAll()
. This method will still return aPage
instead ofSlice
therefore theSELECT COUNT
query is triggered forSlice<T> findAll(...);
.
Description: This is a suite of samples applications that provides different versions of aSlice<T> findAll(...)
method. We have from a minimalist implementation that relies on a hardcoded query as:"SELECT e FROM " + entityClass.getSimpleName() + " e";
(this recipe), to a custom implementation that supports sorting, specification, lock mode and query hints to an implementation that relies on extendingSimpleJpaRepository
.
Key points:
- write an
abstract
class that expose theSlice<T> findAll(...)
methods (SlicePagingRepositoryImplementation
) - implement the
findAll()
methods to returnSlice<T>
(orPage<T>
, but without the total number of elements) - return a
SliceImpl
(Slice<T>
) or aPageImpl
(Page<T>
) without the total number of elements - implement a new
readSlice()
method or override theSimpleJpaRepository#readPage()
page to avoidSELECT COUNT
- pass the entity class (e.g.,
Author.class
) to thisabstract
class via a class repository (AuthorRepository
)
Description: Typically, in offset pagination, there is one query needed for fetching the data and one for counting the total number of records. But, we can fetch this information in a single database rountrip via aSELECT COUNT
subquery nested in the mainSELECT
. Even better, for databases vendors that supportWindow Functions there is a solution relying onCOUNT(*) OVER()
as in this application that uses this window function in a native query against MySQL 8. So, prefer this one instead ofSELECT COUNT
subquery.
Key points:
- create a DTO projection that contains getters for the columns that should be fetched and an extra-column for mapping the return of the
COUNT(*) OVER()
window function - write a native query relying on this window function
Description: When we rely on anoffset paging we have the performance penalty induced by throwing awayn records before reached the desiredoffset. Largern leads to a significant performance penalty. When we have a largen is better to rely onkeyset pagination which maintain a "constant" time for large datasets. In order to understand how badoffset can perform please check thisarticle:
Screenshot from that article (offset pagination):
Need to know if there are more records?
By its nature,keyset doesn't use aSELECT COUNT
to fetch the number of total records. But, with a little tweak, we can easily say if there are more records, therefore to show a button of typeNext Page
. Mainly, if you need such a thing then considerthis application whose climax is listed below:
public AuthorView fetchNextPage(long id, int limit) {
List<Author> authors = authorRepository.fetchAll(id, limit + 1);
if (authors.size() == (limit + 1)) {
authors.remove(authors.size() - 1);
return new AuthorView(authors, true);
}
return new AuthorView(authors, false);
}
Or, like this (rely onAuthor.toString()
method):
public Map<List<Author>, Boolean> fetchNextPage(long id, int limit) {
List<Author> authors = authorRepository.fetchAll(id, limit + 1);
if(authors.size() == (limit + 1)) {
authors.remove(authors.size() -1);
return Collections.singletonMap(authors, true);
}
return Collections.singletonMap(authors, false);
}
APrevious Page
button can be implemented easily based on the first record.
Key points:
- choose the column(s) to act as the latest visited record (e.g.,
id
) - use the column(s) in the
WHERE
andORDER BY
clauses of your SQL
Description: This is a classical Spring Bootoffset pagination example. However, is not advisable to use this approach in production because of its performance penalties explained further.
When we rely on anoffset pagination, we have the performance penalty induced by throwing awayn records before reaching the desiredoffset. Largern leads to a significant performance penalty. Another penalty is the extra-SELECT
needed to count the total number of records. In order to understand how badoffset pagination can perform please checkthis article. A screenshot from that article is below:Nevertheless, maybe this example is a little bit extreme. For relatively small datasets,offset pagination is not so bad (it is close in performance tokeyset pagination), and, since Spring Boot provides built-in support foroffset pagination via the
Page
API, it is very easy to use it. However, depending on the case, we can optimize a little bit theoffset pagination as in the following examples:
Fetch a page as aPage
:
- Trigger
COUNT(*) OVER
And ReturnPage<dto>
- Trigger
COUNT(*) OVER
And ReturnPage<entity>
Via Extra Column - Trigger
SELECT COUNT
Subquery And ReturnPage<dto>
- Trigger
SELECT COUNT
Subquery And ReturnPage<entity>
Via Extra Column - Trigger
SELECT COUNT
Subquery And ReturnPage<projection>
That Maps Entities And The Total Number Of Records Via Projection
Fetch a page as aList
:
- Trigger
COUNT(*) OVER
And ReturnList<dto>
- Trigger
COUNT(*) OVER
And ReturnList<entity>
Via Extra Column - Trigger
SELECT COUNT
Subquery And ReturnList<dto>
- Trigger
SELECT COUNT
Subquery And ReturnList<entity>
Via Extra Column - Trigger
SELECT COUNT
Subquery And ReturnList<projection>
That Maps Entities And The Total Number Of Records Via Projection
But: Ifoffset pagination is causing you performance issues and you decide to go withkeyset pagination then please checkhere (keyset pagination).
Key points of classicaloffset pagination:
- write a repository that extends
PagingAndSortingRepository
- call or write methods that returns
Page<entity>
Examples of classicaloffset pagination:
- call the built-in
findAll(Pageable)
without sorting:repository.findAll(PageRequest.of(page, size));
- call the built-in
findAll(Pageable)
with sorting:repository.findAll(PageRequest.of(page, size, new Sort(Sort.Direction.ASC, "name")));
- use Spring Data query creation to define new methods in your repository:
Page<Author> findByName(String name, Pageable pageable);
Page<Author> queryFirst10ByName(String name, Pageable pageable);
Description: Let's suppose that we have a one-to-many relationship betweenAuthor
andBook
entities. When we save an author, we save his books as well thanks to cascading all/persist. We want to create a bunch of authors with books and save them in the database (e.g., a MySQL database) using the batch technique. By default, this will result in batching each author and the books per author (one batch for the author and one batch for the books, another batch for the author and another batch for the books, and so on). In order to batch authors and books, we need toorder inserts as in this application.
Key points:Beside all setting specific to batching inserts in MySQL, we need to set up inapplication.properties
the following property:spring.jpa.properties.hibernate.order_inserts=true
Example without ordered inserts:
Implementations:
Description: Batch updates in MySQL.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL, statements get rewritten into a single string buffer and sent in a single request) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade all/persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_updates=true
to optimize the batching by ordering updates - before Hibernate 5, we need to set in
application.properties
a setting for enabling batching for versioned entities during update and delete operations (entities that contains@Version
for implicit optimistic locking); this setting is:spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true
; starting with Hibernate 5, this setting should betrue
by default
Output example for single entity:
Output example for parent-child relationship:
Description: Batch deletes that don't involve associations in MySQL.
Note: SpringdeleteAllInBatch()
anddeleteInBatch()
don't use delete batching and don't take advantage of automatic optimstic locking mechanism to preventlost updates (e.g.,@Version
is ignored). They rely onQuery.executeUpdate()
to triggerbulk operations. These operations are fast, but Hibernate doesn’t know which entities are removed, therefore, the Persistence Context is not updated accordingly (it's up to you to flush (before delete) and close/clear (after delete) the Persistence Context accordingly to avoid issues created by unflushed (if any) or outdated (if any) entities). The first one (deleteAllInBatch()
) simply triggers adelete from entity_name
statement and is very useful for deleting all records. The second one (deleteInBatch()
) triggers adelete from entity_name where id=? or id=? or id=? ...
statement, therefore, is prone to cause issues if the generatedDELETE
statement exceedes the maximum accepted size. This issue can be controlled by deleting the data in chunks, relying onIN
operator, and so on.Bulk operations are faster than batching which can be achieved via thedeleteAll()
,deleteAll(Iterable<? extends T> entities)
ordelete()
method. Behind the scene, the two flavors ofdeleteAll()
relies ondelete()
. Thedelete()
/deleteAll()
methods rely onEntityManager.remove()
therefore the Persistence Context is synchronized accordingly. Moreover, if automatic optimstic locking mechanism (to preventlost updates) is enabled then it will be used.
Key points forregular delete batching:
- for deleting in batches rely on
deleteAll()
,deleteAll(Iterable<? extends T> entities)
ordelete()
method - in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL, statements get rewritten into a single string buffer and sent in a single request) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - before Hibernate 5, we need to set in
application.properties
a setting for enabling batching for versioned entities during update and delete operations (entities that contains@Version
for implicit optimistic locking); this setting is:spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true
; starting with Hibernate 5, this setting should betrue
by default
Description: Batch deletes in MySQL viaorphanRemoval=true
.
Note: SpringdeleteAllInBatch()
anddeleteInBatch()
don't use delete batching and don't take advantage of cascading removal,orphanRemoval
and automatic optimstic locking mechanism to preventlost updates (e.g.,@Version
is ignored). They rely onQuery.executeUpdate()
to triggerbulk operations. These operations are fast, but Hibernate doesn’t know which entities are removed, therefore, the Persistence Context is not updated accordingly (it's up to you to flush (before delete) and close/clear (after delete) the Persistence Context accordingly to avoid issues created by unflushed (if any) or outdated (if any) entities). The first one (deleteAllInBatch()
) simply triggers adelete from entity_name
statement and is very useful for deleting all records. The second one (deleteInBatch()
) triggers adelete from entity_name where id=? or id=? or id=? ...
statement, therefore, is prone to cause issues if the generatedDELETE
statement exceedes the maximum accepted size. This issue can be controlled by deleting the data in chunks, relying onIN
operator, and so on.Bulk operations are faster than batching which can be achieved via thedeleteAll()
,deleteAll(Iterable<? extends T> entities)
ordelete()
method. Behind the scene, the two flavors ofdeleteAll()
relies ondelete()
. Thedelete()
/deleteAll()
methods rely onEntityManager.remove()
therefore the Persistence Context is synchronized accordingly. If automatic optimstic locking mechanism (to preventlost updates) is enabled then it will be used. Moreover, cascading removals andorphanRemoval
works as well.
Key points for usingdeleteAll()/delete()
:
- in this example, we have a
Author
entity and each author can have severalBook
(one-to-many) - first, we use
orphanRemoval=true
andCascadeType.ALL
- second, we dissociate all
Book
from the correspondingAuthor
- third, we explicitly (manually) flush the Persistent Context; is time for
orphanRemoval=true
to enter into the scene; thanks to this setting, all disassociated books will be deleted; the generatedDELETE
statements are batched (iforphanRemoval
is set tofalse
, a bunch of updates will be executed instead of deletes) - forth, we delete all
Author
via thedeleteAll()
ordelete()
method (since we have dissaciated allBook
, theAuthor
deletion will take advantage of batching as well)
Description: Batch deletes in MySQL viaON DELETE CASCADE
. Auto-generated database schema will contain theON DELETE CASCADE
directive.
Note: SpringdeleteAllInBatch()
anddeleteInBatch()
don't use delete batching and don't take advantage of cascading removal,orphanRemoval
and automatic optimistic locking mechanism to preventlost updates (e.g.,@Version
is ignored), but both of them take advantage onON DELETE CASCADE
and are very efficient. They triggerbulk operations viaQuery.executeUpdate()
, therefore, the Persistence Context is not synchronized accordingly (it's up to you to flush (before delete) and close/clear (after delete) the Persistence Context accordingly to avoid issues created by unflushed (if any) or outdated (if any) entities). The first one simply triggers adelete from entity_name
statement, while the second one triggers adelete from entity_name where id=? or id=? or id=? ...
statement. For delete in batches rely ondeleteAll()
,deleteAll(Iterable<? extends T> entities)
ordelete()
method. Behind the scene, the two flavors ofdeleteAll()
relies ondelete()
. Mixing batching with database automatic actions (ON DELETE CASCADE
) will result in a partially synchronized Persistent Context.
Key points:
- in this application, we have a
Author
entity and each author can have severalBook
(one-to-many) - first, we remove
orphanRemoval
or set it tofalse
- second, we use only
CascadeType.PERSIST
andCascadeType.MERGE
- third, we set
@OnDelete(action = OnDeleteAction.CASCADE)
next to@OneToMany
- fourth, we set
spring.jpa.properties.hibernate.dialect
toorg.hibernate.dialect.MySQL5InnoDBDialect
(or,MySQL8Dialect
) - fifth, we run through a set of
deleteFoo()
methods that usesbulk and batching deletes as well
Output example:
Alternative implementation: In case that you want to avoid extendingSimpleJpaRepository
check thisimplementation.
Description: This is a SpringBoot application that maps a natural business key using Hibernate@NaturalId
. This implementation allows us to use@NaturalId
as it was provided by Spring.
Key points:
- in the entity (e.g.,
Book
), mark the properties (business keys) that should act as natural IDs with@NaturalId
; commonly, there is a single such property, but multiple are suppored as well ashere - for non-mutable ids, mark the columns as
@NaturalId(mutable = false)
and@Column(nullable = false, updatable = false, unique = true, ...)
- for mutable ids, mark the columns as
@NaturalId(mutable = true)
and@Column(nullable = false, updatable = true, unique = true, ...)
- override the
equals()
andhashCode()
using the natural id(s) - define a
@NoRepositoryBean
interface (NaturalRepository
) to define two methods, namedfindBySimpleNaturalId()
andfindByNaturalId()
- provide an implementation for this interface (
NaturalRepositoryImpl
) relying on Hibernate,Session
,bySimpleNaturalId()
andbyNaturalId()
methods - use
@EnableJpaRepositories(repositoryBaseClass = NaturalRepositoryImpl.class)
to register this implementation as the base class - for the entity, write a classic repository
- inject this class in your services and call
findBySimpleNaturalId()
orfindByNaturalId()
Description: This is a Spring Boot application that usesP6Spy.P6Spy is a framework that enables database data to be seamlessly intercepted and logged with no code changes to the application.
Key points:
- in
pom.xml
, add the P6Spy Maven dependency - in
application.properties
, set up JDBC URL as,jdbc:p6spy:mysql://localhost:3306/db_users
- in
application.properties
, set up driver class name as,com.p6spy.engine.spy.P6SpyDriver
- in the application root folder add the file
spy.properties
(this file contains P6Spy configurations); in this application, the logs will be outputed to console, but you can easy switch to a file; more details about P6Spy configurations can be found in documentation
Note: Optimistic locking mechanism via@Version
works for detached entities as well.
Description: This is a Spring Boot application that simulates a scenario that leads to an optimistic locking exception. When such exception occur, the application retry the corresponding transaction viadb-util library developed by Vlad Mihalcea.
Key points:
- for Maven, in
pom.xml
, add thedb-util
dependency - configure the
OptimisticConcurrencyControlAspect
bean - mark the method (not annotated with
@Transactional
) that is prone to throw (or that calls a method that is prone to throw (this method can be annotated with@Transactional
)) an optimistic locking exception with@Retry(times = 10, on = OptimisticLockingFailureException.class)
Note: Optimistic locking mechanism via Hibernate version-less doesn't work for detached entities (don't close the Persistent Context).
Description: This is a Spring Boot application that simulates a scenario that leads to an optimistic locking exception (e.g., in Spring Boot,OptimisticLockingFailureException
) via Hibernate version-less optimistic locking. When such exception occur, the application retry the corresponding transaction viadb-util library developed by Vlad Mihalcea.
Key points:
- for Maven, in
pom.xml
, add thedb-util
library dependency - configure the
OptimisticConcurrencyControlAspect
bean - annotate the corresponding entity (e.g.,
Inventory
) with@DynamicUpdate
and@OptimisticLocking(type = OptimisticLockType.DIRTY)
- mark the method (not annotated with
@Transactional
) that is prone to throw (or that calls a method that is prone to throw (this method can be annotated with@Transactional
)) an optimistic locking exception with@Retry(times = 10, on = OptimisticLockingFailureException.class)
Note: You may also like to read the recipe,"How To Create DTO Via Spring Data Projections"
Description: This is an application sample that fetches only the needed columns from the database via Spring Data Projections (DTO) and enrich the result via virtual properties.
Key points:
- we fetch from the database only the author
name
andage
- in the projection interface,
AuthorNameAge
, use the@Value
and Spring SpEL to point to a backing property from the domain model (in this case, the domain model propertyage
is exposed via the virtual propertyyears
) - in the projection interface,
AuthorNameAge
, use the@Value
and Spring SpEL to enrich the result with two virtual properties that don't have a match in the domain model (in this case,rank
andbooks
)
Description: Spring Data comes with the query creation mechanism for JPA that is capable to interpret a query method name and convert it into a SQL query in the proper dialect. This is possible as long as we respect the naming conventions of this mechanism. This is an application that exploit this mechanism to write queries that limit the result size. Basically, the name of the query method instructs Spring Data how to add theLIMIT
(or similar clauses depending on the RDBMS) clause to the generated SQL queries.
Key points:
- define a Spring Data regular repository (e.g.,
AuthorRepository
) - write query methods respecting the query creation mechanism for JPA naming conventions
Examples:
-List<Author> findFirst5ByAge(int age);
-List<Author> findFirst5ByAgeGreaterThanEqual(int age);
-List<Author> findFirst5ByAgeLessThan(int age);
-List<Author> findFirst5ByAgeOrderByNameDesc(int age);
-List<Author> findFirst5ByGenreOrderByAgeAsc(String genre);
-List<Author> findFirst5ByAgeGreaterThanEqualOrderByNameAsc(int age);
-List<Author> findFirst5ByGenreAndAgeLessThanOrderByNameDesc(String genre, int age);
-List<AuthorDto> findFirst5ByOrderByAgeAsc();
-Page<Author> queryFirst10ByName(String name, Pageable p);
-Slice<Author> findFirst10ByName(String name, Pageable p);
The list of supported keywords is listed below:
Note: As a rule, in real applications avoid generating schema viahibernate.ddl-auto
or set it tovalidate
. Useschema-*.sql
file or betterFlyway
orLiquibase
migration tools.
Description: This application is an example of usingschema-*.sql
to generate a schema(database) in MySQL.
Key points:
- in
application.properties
, set the JDBC URL (e.g.,spring.datasource.url=jdbc:mysql://localhost:3306/bookstoredb?createDatabaseIfNotExist=true
) - in
application.properties
, disable DDL auto (just don't add explicitly thehibernate.ddl-auto
setting) - in
application.properties
, instruct Spring Boot to initialize the schema fromschema-mysql.sql
file
Note: As a rule, in real applications avoid generating schema viahibernate.ddl-auto
or set it tovalidate
. Useschema-*.sql
file or betterFlyway
orLiquibase
.
Description: This application is an example of usingschema-*.sql
to generate two databases in MySQL. The databases are matched at entity mapping via@Table
.
Key points:
- in
application.properties
, set the JDBC URL without the database, e.g.,spring.datasource.url=jdbc:mysql://localhost:3306
- in
application.properties
, disable DDL auto (just don't specifyhibernate.ddl-auto
) - in
aaplication.properties
, instruct Spring Boot to initialize the schema fromschema-mysql.sql
file - in
Author
entity, specify that the corresponding table (author
) is in the databaseauthorsdb
via@Table(schema="authorsdb")
- in
Book
entity, specify that the corresponding table (book
) is in the databasebooksdb
via@Table(schema="booksdb")
Output example:
- Persisting a
Author
results in the following SQL:insert into authorsdb.author (age, genre, name) values (?, ?, ?)
- Persisting a
Book
results the following SQL:insert into booksdb.book (isbn, title) values (?, ?)
Note: For web-applications, pagination should be the way to go, not streaming. But, if you choose streaming then keep in mind the golden rule: keep th result set as small as posible. Also, keep in mind that the Execution Plan might not be as efficient as when using SQL-level pagination.
Description: This application is an example of streaming the result set via Spring Data and MySQL. This example can be adopted for databases that fetches the entire result set in a single roundtrip causing performance penalties.
Key points:
- rely onforward-only result set (default in Spring Data)
- rely onread-only statement (add
@Transactional(readOnly=true)
) - set the fetch-size set (e.g. 30, or row-by-row;
Integer.MIN_VALUE
(recommended in MySQL)) - for MySQL, set
Statement
fetch-size toInteger.MIN_VALUE
, or adduseCursorFetch=true
to the JDBC URL and setStatement
fetch-size to a positive integer (e.g., 30)
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is an example of migrating a MySQL database via Flyway when the database exists (it is created before migration via MySQL specific parameter,createDatabaseIfNotExist=true
).
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
- in
application.properties
, set the JDBC URL as follows:jdbc:mysql://localhost:3306/bookstoredb?createDatabaseIfNotExist=true
- each SQL file containing the schema update add it in
classpath:db/migration
- each SQL file name it as
V1.1__Description.sql
,V1.2__Description.sql
, ...
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is an example of migrating a MySQL database when the database is created by Flyway viaspring.flyway.schemas
. In this case, the entities should be annotated with@Table(schema = "bookstoredb")
or@Table(catalog = "bookstoredb")
. Here, the database name isbookstoredb
.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
- in
application.properties
, set the JDBC URL as follows:jdbc:mysql://localhost:3306/
- in
application.properties
, addspring.flyway.schemas=bookstoredb
, wherebookstoredb
is the database that should be created by Flyway (feel free to add your own database name) - each entity that should be stored in this database should be annotated with,
@Table(schema/catalog = "bookstoredb")
- each SQL file containing the schema update add it in
classpath:db/migration
- each SQL file name it as
V1.1__Description.sql
,V1.2__Description.sql
, ...
Output of migration history example:
Note: For production don't rely onhibernate.ddl-auto
to create your schema. Remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is an example of auto-creating and migrating schemas for MySQL and PostgreSQL. In addition, each data source uses its own HikariCP connection pool. In case of MySQL, whereschema=database, we auto-create the schema (authorsdb
) based oncreateDatabaseIfNotExist=true
. In case of PostgreSQL, where a database can have multiple schemas, we use the defaultpostgres
database and auto-create in it the schema,booksdb
. For this we rely on Flyway, which is capable to create a missing schema.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
- in
application.properties
, configure the JDBC URL for MySQL as,jdbc:mysql://localhost:3306/authorsdb?createDatabaseIfNotExist=true
and for PostgreSQL as,jdbc:postgresql://localhost:5432/postgres?currentSchema=booksdb
- in
application.properties
, setspring.flyway.enabled=false
to disable default behavior - programmatically create a
DataSource
for MySQL and one for PostgreSQL - programmatically create a
FlywayDataSource
for MySQL and one for PostgreSQL - programmatically create an
EntityManagerFactory
for MySQL and one for PostgreSQL - for MySQL, place the migration SQLs files in
db\migration\mysql
- for PostgreSQL, place the migration SQLs files in
db\migration\postgresql
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is an example of auto-creating and migrating two schemas in PostgreSQL using Flyway. In addition, each data source uses its own HikariCP connection pool. In case of PostgreSQL, where a database can have multiple schemas, we use the defaultpostgres
database and auto-create two schemas,authors
andbooks
. For this we rely on Flyway, which is capable to create the missing schemas.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
- in
application.properties
, configure the JDBC URL forbooks
asjdbc:postgresql://localhost:5432/postgres?currentSchema=books
and forauthors
asjdbc:postgresql://localhost:5432/postgres?currentSchema=authors
- in
application.properties
, setspring.flyway.enabled=false
to disable default behavior - programmatically create two
DataSource
, one forbooks
and one forauthors
- programmatically create two
FlywayDataSource
, one forbooks
and one forauthors
- programmatically create two
EntityManagerFactory
, one forbooks
and one forauthors
- for
books
, place the migration SQLs files indb\migration\books
- for
authors
, place the migration SQLs files indb\migration\authors
Description: This application is an example applyingJOIN FETCH
to fetch an@ElementCollection
.
Key points:
- by default,
@ElementCollection
is loaded lazy, keep it lazy - use
JOIN FETCH
in the repository
Note: Consider using@Subselect
only if using DTO, DTO and extra queries, or map a database view to an entity is not a solution.
Description: This application is an example of mapping an entity to a query via Hibernate,@Subselect
. Mainly, we have two entities in a bidirectionalone-to-many association. AnAuthor
has wrote severalBook
. The idea is to write aread-only query to fetch fromAuthor
only some fields (e.g., DTO), but to have the posibility to callgetBooks()
and fetch theBook
in a lazy manner as well. As you know, a classic DTO cannot be used, since such DTO is not managed and we cannot navigate the associations (don’t support any managed associations to other entities). Via Hibernate@Subselect
we can map aread-only andimmutable entity to a query. This time, we can lazy navigate the associations.
Key points:
- define a new entity that contains only the needed fields from the
Author
(including association toBook
) - for these fields, define only getters
- mark the entity as
@Immutable
since no write operations are allowed - flush pending state transitions for the used entities by
@Synchronize
- use
@Subselect
to write the needed query, map an entity to an SQL query
Description: This application is an example of using Hibernate soft deletes in a Spring Boot application.
Key points:
- define an
abstract
classBaseEntity
with a field nameddeleted
- the entities (e.g.,
Author
andBook
entities) that should take advantage of soft deletes should extendBaseEntity
- these entities should be marked with Hibernate,
@Where
annotation like this:@Where(clause = "deleted = false")
- these entities should be marked with Hibernate,
@SQLDelete
annotation to triggerUPDATE
SQLs in place ofDELETE
SQLs, as follows:@SQLDelete(sql = "UPDATE author SET deleted = true WHERE id = ?")
- for fetching all entities including those marked as deleted or for fetching only the entities marked as deleted we need to rely on SQL native queries
If you use thespring-boot-starter-jdbc
orspring-boot-starter-data-jpa
"starters", you automatically get a dependency to HikariCP
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up HikariCP viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users. Check the HickariCP report revealing the connection pool status.
Key points:
- write a
@Bean
that returns theDataSource
programmatically
Description: Auditing is useful for maintaining history records. This can later help us in tracking user activities.
Key points:
- create an
abstract
base entity (e.g.,BaseEntity
) and annotate it with@MappedSuperclass
and@EntityListeners({AuditingEntityListener.class})
- in this base entity, add the following fields that will be automatically persisted:
-@CreatedDate protected LocalDateTime created;
-@LastModifiedDate protected LocalDateTime lastModified;
-@CreatedBy protected U createdBy;
-@LastModifiedBy protected U lastModifiedBy;
- enable auditing via
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
- provide an implementation for
AuditorAware
(this is needed for persisting the user that performed the modification; use Spring Security to return the currently logged-in user) - expose this implementation via
@Bean
- entites that should be audited should extend the base entity
- store the date-time in database in UTC
Description: Auditing is useful for maintaining history records. This can later help us in tracking user activities.
Key points:
- each entity that should be audited should be annotated with
@Audited
- optionally, annotate entities with
@AuditTable
to rename the table used for auditing - rely on
ValidityAuditStrategy
for fast database reads, but slower writes (slower than the defaultDefaultAuditStrategy
)
Description: By default, the attributes of an entity are loaded eager (all at once). This application is an alternative toHow To Use Hibernate Attribute Lazy Loading fromhere. This application uses a base class to isolate the attributes that should be loaded eagerly and subentities (entities that extends the base class) for isolating the attributes that should be loaded on demand.
Key points:
- create the base class (this is not an entity),
BaseAuthor
, and annotate it with@MappedSuperclass
- create
AuthorShallow
subentity ofBaseAuthor
and don't add any attribute in it (this will inherit the attributes from the superclass) - create
AuthorDeep
subentity ofBaseAuthor
and add to it the attributes that should be loaded on demand (e.g.,avatar
) - map both subentities to the same table via
@Table(name = "author")
- provide the typical repositories,
AuthorShallowRepository
andAuthorDeepRepository
Run the following requests (via BookstoreController):
- fetch all authors shallow (without avatars):
localhost:8080/authors/shallow
- fetch all authors deep (with avatars):
localhost:8080/authors/deep
Check as well:
- Attribute Lazy Loading (basic)
- Default Values For Lazy Loaded Attributes
- Attribute Lazy Loading And Jackson Serialization
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely on constructor and Spring Data Query Builder Mechanism.
Key points:
- write a proper constructor in the DTO class
- rely on Spring Data Query Builder Mechanism to write the SQL
- for using Spring Data Projections check thisitem
See also:
Dto Via Constructor Expression and JPQL
Description: UsingJOIN
is very useful for fetching DTOs (data that is never modified, not in the current or subsequent requests). For example, consider two entities,Author
andBook
in a lazy-bidirectional@OneToMany
association. And, we want to fetch a subset of columns from the parent table (author
) and a subset of columns from the child table (book
). This job is a perfect fit forJOIN
which can pick up columns from different tables and build araw result set. This way we fetch only the needed data. Moreover, we may want to serve the result set in pages (e.g., viaLIMIT
). This application contains several approaches for accomplishing this task with offset pagination.
Key points:
- pagination via
Page
(withSELECT COUNT
andCOUNT(*) OVER()
window function) - pagination via
Slice
andList
- pagination via
DENSE_RANK()
for avoiding the truncation of the result set (an author can be fetched with only a subset of his books)
See also:
Description: Let's assume that we have two entities engaged in a one-to-many (or many-to-many) lazy bidirectional (or unidirectional) relationship (e.g.,Author
has moreBook
). And, we want to trigger a singleSELECT
that fetches allAuthor
and the correspondingBook
. This is a job forJOIN FETCH
which is converted behind the scene into aINNER JOIN
. Being anINNER JOIN
, the SQL will return onlyAuthor
that haveBook
. If we want to return allAuthor
, including those that doesn't haveBook
, then we can rely onLEFT JOIN FETCH
. Similar, we can fetch allBook
, including those with no registeredAuthor
. This can be done viaLEFT JOIN FETCH
orLEFT JOIN
.
Key points:
- define two related entities (e.g.,
Author
andBook
in a one-to-many lazy bidirectional relationship) - write a JPQL
LEFT JOIN FETCH
to fetch all authors and books (fetch authors even if they don't have registered books) - write a JPQL
LEFT JOIN FETCH
to fetch all books and authors (fetch books even if they don't have registered authors)
See also:
Description: This is an application meant to reveal the differences betweenJOIN
andJOIN FETCH
. The important thing to keep in mind is that, in case ofLAZY
fetching,JOIN
will not be capable to initialize the associated collections along with their parent objects using a single SQLSELECT
. On the other hand,JOIN FETCH
is capable to accomplish this kind of task. But, don't underestimateJOIN
, becauseJOIN
is the proper choice when we need to combine/join the columns of two (or more) tables in the same query, but we don't need to initialize the associated collections on the returned entity (e.g., very useful for fetching DTO).
Key points:
- define two related entities (e.g.,
Author
andBook
in a one-to-many lazy-bidirectional relationship) - write a JPQL
JOIN
andJOIN FETCH
to fetch an author including his books - write a JPQL
JOIN
to fetch a book (1) - write a JPQL
JOIN
to fetch a book including its author (2) - write a
JOIN FETCH
to fetch a book including its author
Notice that:
- via
JOIN
, fetchingBook
ofAuthor
requires additionalSELECT
statements being prone to N+1 performance penalty - via
JOIN
(1), fetchingAuthor
ofBook
requires additionalSELECT
statements being prone to N+1 performance penalty - via
JOIN
(2), fetchingAuthor
ofBook
works exactly asJOIN FETCH
(requires a singleSELECT
) - via
JOIN FETCH
, fetching eachAuthor
of aBook
requires a singleSELECT
Description: If, for some reason, you need an entity in your Spring projection (DTO), then this application shows you how to do it via an example. In this case, there are two entities,Author
andBook
, involved in a lazy bidirectional one-to-many association (it can be other association as well, or even no materialized association). And, we want to fetch in a Spring projection the authors as entities,Author
, and thetitle
of the books.
Key points:
- define two related entities (e.g.,
Author
andBook
in a one-to-many lazy bidirectional relationship) - define the proper Spring projection having
public Author getAuthor()
andpublic String getTitle()
- write a JPQL to fetch data
Description: If, for some reason, you need an entity in your Spring projection (DTO), then this application shows you how to do it via an example. In this case, there are two entities,Author
andBook
, that have no materialized association between them, but, they share thegenre
attribute. We use this attribute to join authors with books via JPQL. And, we want to fetch in a Spring projection the authors as entities,Author
, and thetitle
of the books.
Key points:
- define two unrelated entities (e.g.,
Author
andBook
) - define the proper Spring projection having
public Author getAuthor()
andpublic String getTitle()
- write a JPQL to fetch data
Description: Let's assume that we have two entities,Author
andBook
. There is no materialized association between them, but, both entities shares an attribute named,genre
. We want to use this attribute to join the tables corresponding toAuthor
andBook
, and fetch the result in a DTO. The result should contain theAuthor
entity and only thetitle
attribute fromBook
. Well, when you are in a scenario as here, it is strongly advisable to avoid fetching the DTO viaconstructor expression. This approach cannot fetch the data in a singleSELECT
, and is prone to N+1. Way better than this consists of using Spring projections, JPATuple
or even HibernateResultTransformer
. These approaches will fetch the data in a singleSELECT
. This application is aDON'T DO THIS example. Check the number of queries needed for fetching the data. In place, do it as here:Entity Inside Spring Projection (no association).
Description: This application is an example of fetching a DTO that includes attributes from an@ElementCollection
.
Key points:
- by default,
@ElementCollection
is loaded lazy, keep it lazy - use a Spring projection and
JOIN
in the repository
Description: In case of@ManyToMany
association, we always should rely onSet
(not onList
) for mapping the collection of associated entities (entities of the other parent-side). Why? Well, please seePrefer Set Instead of List in @ManyToMany Relationships. But, is well-known thatHashSet
doesn't have a predefined entry order of elements. If this is an issue then this application relies on@OrderBy
which adds anORDER BY
clause in the SQL statement. The database will handle the ordering. Further, Hibernate will preserve the order via aLinkedHashSet
.
This application uses two entities,Author
andBook
, involved in a lazy bidirectional many-to-many relationship. First, we fetch aBook
by title. Further, we callgetAuthors()
to fetch the authors of this book. The fetched authors are ordered descending by name. The ordering is done by the database as a result of adding@OrderBy("name DESC")
, and is preserved by Hibernate.
Key points:
- ask the database to handle ordering and Hibernate to preserve this order via
@OrderBy
- this works with
HashSet
, but doesn't provide consistency across all transition states (e.g.,transient state) - for consistency across thetransient state as well, consider using explicitly
LinkedHashSet
instead ofHashSet
Note: Alternatively, we can use@OrderColumn
. This gets materialized in an additional column in the junction table. This is needed for maintaining a permanent ordering of the related data.
Description: This is a sample application that shows how versioned (@Version
) optimistic locking and detached entity works. Running the application will result in an optimistic locking specific exception (e.g., the Spring Boot specific,OptimisticLockingFailureException
).
Key points:
- in a transaction, fetch an entity via
findById(1L)
; commit transaction and close the Persistence Context - in a second transaction, fetch another entity via
findById(1L)
and update it; commit the transaction and close the Persistence Context - outside transactional context, update the detached entity (fetched in the first transaction)
- in a third transaction, call
save()
and pass to it the detached entity; trying to merge (EntityManager.merge()
) the entity will end up in an optimistic locking exception since the version of the detached and just loaded entity don't match
Note: Optimistic locking via@Version
works for detached entities as well.
Description: This is a Spring Boot application that simulates a scenario that leads to an optimistic locking exception. So, running the application should end up with a Spring specificObjectOptimisticLockingFailureException
exception.
Key points:
- set up versioned optimistic locking mechanism
- rely on two concurrent threads that call the same
@Transactional
method used for updating data
Note: Optimistic locking via@Version
works for detached entities as well.
Description: This is a Spring Boot application that simulates a scenario that leads to an optimistic locking exception. When such exception occurs, the application retry the corresponding transaction viadb-util library developed by Vlad Mihalcea.
Key points:
- in
pom.xml
, add thedb-util
dependency - configure the
OptimisticConcurrencyControlAspect
bean - rely on
TransactionTemplate
Note: Version-less optimistic locking doesn't work for detached entities (do not close the Persistence Context).
Description: This is a Spring Boot application that simulates a scenario that leads to an optimistic locking exception. So, running the application should end up with a Spring specificObjectOptimisticLockingFailureException
exception.
Key points:
- set up the version-less optimistic locking mechanism
- rely on two concurrent threads that call the same a
@Transactional
method used for updating data
Note: Version-less optimistic locking doesn't work for detached entities (do not close the Persistence Context).
Description: This is a Spring Boot application that simulates a scenario that leads to an optimistic locking exception. When such exception occur, the application retry the corresponding transaction viadb-util library developed by Vlad Mihalcea.
Key points:
- for Maven, in
pom.xml
, add thedb-util
dependency - configure the
OptimisticConcurrencyControlAspect
bean - rely on
TransactionTemplate
Description: This is a sample application that shows how to take advantage of versioned optimistic locking and detached entities in HTTP long conversations. The climax consists of storing the detached entities across multiple HTTP requests. Commonly, this can be accomplished via HTTP session.
Key points:
- prepare the entity via
@Version
- rely on
@SessionAttributes
for storing the detached entities
Sample output (check the message caused by optimistic locking exception):
Note: Rely on this approach only if you simply cannot useJOIN FETCH WHERE
or@NamedEntityGraph
.
Description: This application is a sample of using Hibernate@Where
for filtering associations.
Key points:
- use
@Where(clause = "condition to be met")
in entity (check theAuthor
entity)
Description: Batch inserts (in MySQL) in Spring Boot style.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since the Hibernate
IDENTITY
will cause insert batching to be disabled - if is not needed then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: Typically, in offset pagination, there is one query needed for fetching the data and one for counting the total number of records. But, we can fetch this information in a single database rountrip via aSELECT COUNT
subquery nested in the mainSELECT
. Even better, for databases vendors that supportWindow Functions there is a solution relying onCOUNT(*) OVER()
as in this application that uses this window function in a native query against MySQL 8. So, prefer this one instead ofSELECT COUNT
subquery.This application fetches data asPage<entity>
via Spring Boot offset pagination, but, if the fetched data isread-only, then rely onPage<dto>
ashere.
Key points:
- write a repository that extends
PagingAndSortingRepository
- in the entity, add an extra column for representing the total number of records and annotate it as
@Column(insertable = false, updatable = false)
- fetch data via a native query (that includes counting) into a
List<entity>
- use the fetched
List<entity>
andPageable
to create aPage<entity>
Description: This application fetches data asList<entity>
via Spring Boot offset pagination. TheSELECT COUNT
triggered for counting the total number of records is a subquery of the mainSELECT
. Therefore, there will be a single database roundtrip instead of two (typically, one query is needed for fetching the data and one for counting the total number of records).
Key points:
- write a repository that extends
PagingAndSortingRepository
- in the
entity
, add an extra column for representing the total number of records and annotate it as@Column(insertable = false, updatable = false)
- fetch data via a native query (that includes
SELECT COUNT
subquery) into aList<entity>
Description: This application fetches data asList<projection>
via Spring Boot offset pagination. The projection maps the entity and the total number of records. This information is fetched in a single database rountrip because theSELECT COUNT
triggered for counting the total number of records is a subquery of the mainSELECT
. Therefore, there will be a single database roundtrip instead of two (typically, there is one query needed for fetching the data and one for counting the total number of records). Use this approch only if the fetched data is notread-only. Otherwise, preferList<dto>
ashere.
Key points:
- write a Spring projection that maps the entity and the total number of records
- write a repository that extends
PagingAndSortingRepository
- fetch data via a JPQL query (that includes
SELECT COUNT
subquery) into aList<projection>
Description: Typically, in offset pagination, there is one query needed for fetching the data and one for counting the total number of records. But, we can fetch this information in a single database rountrip via aSELECT COUNT
subquery nested in the mainSELECT
. Even better, for databases vendors that supportWindow Functions there is a solution relying onCOUNT(*) OVER()
as in this application that uses this window function in a native query against MySQL 8. So, prefer this one instead ofSELECT COUNT
subquery.This application fetches data asList<entity>
via Spring Boot offset pagination, but, if the fetched data isread-only, then rely onList<dto>
ashere.
Key points:
- write a repository that extends
PagingAndSortingRepository
- in the
entity
, add an extra column for representing the total number of records and annotate it as@Column(insertable = false, updatable = false)
- fetch data via a native query (that includes
COUNT(*) OVER
subquery) into aList<entity>
Description: This application fetches data asPage<entity>
via Spring Boot offset pagination. Use this only if the fetched data will be modified. Otherwise, fetchPage<dto>
ashere. TheSELECT COUNT
triggered for counting the total number of records is a subquery of the mainSELECT
. Therefore, there will be a single database roundtrip instead of two (typically, there is one query needed for fetching the data and one for counting the total number of records).
Key points:
- write a repository that extends
PagingAndSortingRepository
- in the entity, add an extra column for representing the total number of records and annotate it as
@Column(insertable = false, updatable = false)
- fetch data via a native query (that includes counting) into a
List<entity>
- use the fetched
List<entity>
andPageable
to create aPage<entity>
Description: This application fetches data asPage<projection>
via Spring Boot offset pagination. The projection maps the entity and the total number of records. This information is fetched in a single database rountrip because theSELECT COUNT
triggered for counting the total number of records is a subquery of the mainSELECT
.
Key points:
- define a Spring projection that maps the entity and the total number of records
- write a repository that extends
PagingAndSortingRepository
- fetch data via a JPQL query into a
List<projection>
- use the fetched
List<projection>
andPageable
to create aPage<projection>
Description: Typically, in offset pagination, there is one query needed for fetching the data and one for counting the total number of records. But, we can fetch this information in a single database rountrip via aSELECT COUNT
subquery nested in the mainSELECT
. Even better, for databases vendors that supportWindow Functions there is a solution relying onCOUNT(*) OVER()
as in this application that uses this window function in a native query against MySQL 8. So, prefer this one instead ofSELECT COUNT
subquery. This application return aPage<dto>
.
Key points:
- create a Spring projection (DTO) to contains getters only for the columns that should be fetched
- write a repository that extends
PagingAndSortingRepository
- fetch data via a native query (that includes counting) into a
List<dto>
- use the fetched
List<dto>
andPageable
to create aPage<dto>
Story: Spring Boot provides anoffset based built-in paging mechanism that returns aPage
orSlice
. Each of these APIs represents a page of data and some metadata. The main difference is thatPage
contains the total number of records, whileSlice
can only tell if there is another page available. ForPage
, Spring Boot provides afindAll()
method capable to take as arguments aPageable
and/or aSpecification
orExample
. In order to create aPage
that contains the total number of records, this method triggers anSELECT COUNT
extra-query next to the query used to fetch the data of the current page . This can be a performance penalty since theSELECT COUNT
query is triggered every time we request a page. In order to avoid this extra-query, Spring Boot provides a more relaxed API, theSlice
API. UsingSlice
instead ofPage
removes the need of this extraSELECT COUNT
query and returns the page (records) and some metadata without the total number of records. So, whileSlice
doesn't know the total number of records, it still can tell if there is another page available after the current one or this is the last page. The problem is thatSlice
work fine for queries containing the SQL,WHERE
clause (including those that uses the query builder mechanism built into Spring Data), but itdoesn't work forfindAll()
. This method will still return aPage
instead ofSlice
therefore theSELECT COUNT
query is triggered forSlice<T> findAll(...);
.
Workaround:The trick is to simply define a method namedfetchAll()
that uses JPQL andPageable
to returnSlice<entity>
, and a method namedfetchAllDto()
that uses JPQL andPageable
as well to returnSlice<dto>
. So, avoid naming the methodfindAll()
.
Usage example:public Slice<Author> fetchNextSlice(int page, int size) {
return authorRepository.fetchAll(PageRequest.of(page, size, new Sort(Sort.Direction.ASC, "age")));
}
public Slice<AuthorDto> fetchNextSliceDto(int page, int size) {
return authorRepository.fetchAllDto(PageRequest.of(page, size, new Sort(Sort.Direction.ASC, "age")));
}
Description: This application is a proof of concept for using Spring Projections(DTO) and inclusive full joins written in native SQL (for MySQL).
Key points:
- define two entities (e.g.,
Author
andBook
in a lazy bidirectional@OneToMany
relationship) - populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (projections) that contains getters for the columns that should be fetched from the database (e.g., check
AuthorNameBookTitle.java
) - write inclusive full joins queries using native SQL
Description: This application is a sample of declaring an immutable entity. Moreover, the immutable entity will be stored in Second Level Cache viaEhCache
implementation.
Key points of declaring an immutable entity:
- annotate the entity with
@Immutable (org.hibernate.annotations.Immutable)
- avoid any kind of associations
- set
hibernate.cache.use_reference_entries configuration
totrue
If you use thespring-boot-starter-jdbc
orspring-boot-starter-data-jpa
"starters", you automatically get a dependency to HikariCP
Note: The best way to tune the connection pool parameters consist in usingFlexy Pool by Vlad Mihalcea. ViaFlexy Pool you can find the optim settings that sustain high-performance of your connection pool.
Description: This is a kickoff application that set up HikariCP viaDataSourceBuilder
. ThejdbcUrl
is set up for a MySQL database. For testing purposes, the application uses anExecutorService
for simulating concurrent users. Check the HickariCP report revealing the connection pool status.
Key points:
- write a
@Bean
that returns theDataSource
programmatically
Description: This is a SpringBoot - MySQL application that maps a natural business key using Hibernate@NaturalId
. This implementation allows us to use@NaturalId
as it was provided by Spring. Moreover, this application uses Second Level Cache (EhCache
) and@NaturalIdCache
for skipping the entity identifier retrieval from the database.
Key points:
- enable Second Level Cache (
EhCache
) - annotate entity with
@NaturalIdCache
for caching natural ids - optionally, annotate entity with
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "Book")
for caching entites as well
Output sample (for MySQL withIDENTITY
generator,@NaturalIdCache
and@Cache
):
Description: This application is an example of calculating a non-persistent property of an entity based on the persistent entity attributes. In this case, we will use JPA,@PostLoad
.
Key points:
- annotate the non-persistent field and property with
@Transient
- define a method annotated with
@PostLoad
that calculates this non-persistent property based on the persistent entity attributes
Description: This application is an example of calculating an entity persistent property atINSERT
and/orUPDATE
time via Hibernate,@Generated
.
Key points:
Calculate atINSERT
time:
- annotate the corresponding persistent field with
@Generated(value = GenerationTime.INSERT)
- annotate the corresponding persistent field with
@Column(insertable = false)
Calculate atINSERT
andUPDATE
time:
- annotate the corresponding persistent field with
@Generated(value = GenerationTime.ALWAYS)
- annotate the corresponding persistent field with
@Column(insertable = false, updatable = false)
Further, apply:
Method 1:
- if the database schema is generated via JPA annotations (not recommended) then use
columnDefinition
element of@Column
to specify as an SQL query expression the formula for calculating the persistent property
Method 2:
- if the database schema is not generated via JPA annotations (recommended way) then add the formula as part of schema in
CREATE TABLE
Note: In production, you should not rely oncolumnDefinition
. You should disablehibernate.ddl-auto
(by omitting it) or set it tovalidate
, and add the SQL query expression inCREATE TABLE
(in this application, check thediscount
column inCREATE TABLE
, fileschema-sql.sql
). Nevertheless, not evenschema-sql.sql
is ok in production. The best way is to rely on Flyway or Liquibase.
Description: This application is an example of calculating a non-persistent property of an entity based on the persistent entity attributes. In this case, we will use Hibernate,@Formula
.
Key points:
- annotate the non-persistent property with
@Transient
- annotate the non-persistent field with
@Formula
- as the value of
@Formula
add the SQL query expression that calculates this non-persistent property based on the persistent entity attributes
Note: The same thing can be obtained via Spring Data JPA auditing ashere.
Description: This application is an example of adding in an entity the fields,created
,createdBy
,lastModified
andlastModifiedBy
via Hibernate support. These fields will be automatically generated/populated.
Key points:
- write an
abstract
class (e.g.,BaseEntity
) annotated with@MappedSuperclass
- in this
abstract
class, define a field namedcreated
and annotate it with the built-in@CreationTimestamp
annotation - in this
abstract
class, define a field namedlastModified
and annotate it with the built-in@UpdateTimestamp
annotation - in this
abstract
class, define a field namedcreatedBy
and annotate it with the@CreatedBy
annotation - in this
abstract
class, define a field namedlastModifiedBy
and annotate it with the@ModifiedBy
annotation - implement the
@CreatedBy
annotation viaAnnotationValueGeneration
- implement the
@ModifiedBy
annotation viaAnnotationValueGeneration
- every entity that want to take advantage of
created
,createdBy
,lastModified
andlastModifiedBy
will extend theBaseEntity
- store the date-time in UTC
Description: Auditing is useful for maintaining history records. This can later help us in tracking user activities.
Key points:
- each entity that should be audited should be annotated with
@Audited
- optionally, annotate entities with
@AuditTable
to rename the table used for auditing - rely on
ValidityAuditStrategy
for fast database reads, but slower writes (slower than the defaultDefaultAuditStrategy
) - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
for avoiding schema generated from JPA annotations - create
schema-mysql.sql
and provide the SQL statements needed by Hibernate Envers - if the schema is not automatically found, then point it via
spring.jpa.properties.org.hibernate.envers.default_catalog
for MySQL orspring.jpa.properties.org.hibernate.envers.default_schema
for the rest
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is a kickoff for setting Flyway and MySQLDataSource
programmatically.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
- configure
DataSource
and Flyway programmatically
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is an example of migrating a PostgreSQL database via Flyway for the default databasepostgres
and schemapublic
.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
- in
application.properties
, set the JDBC URL as follows:jdbc:postgresql://localhost:5432/postgres
- each SQL file containing the schema update add it in
classpath:db/migration
- each SQL file name it as
V1.1__Description.sql
,V1.2__Description.sql
, ...
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is an example of migrating a schema (bookstore
) created by Flyway viaspring.flyway.schemas
in the defaultpostgres
database. In this case, the entities should be annotated with@Table(schema = "bookstore")
.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
- in
application.properties
, set the JDBC URL as follows:jdbc:postgresql://localhost:5432/postgres
- in
application.properties
, addspring.flyway.schemas=bookstore
, wherebookstore
is the schema that should be created by Flyway in thepostgres
database (feel free to add your own database name) - each entity that should be stored in this database should be annotated with,
@Table(schema = "bookstore")
- each SQL file containing the schema update add it in
classpath:db/migration
- each SQL file name it as
V1.1__Description.sql
,V1.2__Description.sql
, ...
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is a kickoff for setting Flyway and PostgreSQLDataSource
programmatically.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
- configure
DataSource
and Flyway programmatically
Note: For production, don't rely onhibernate.ddl-auto
(or counterparts) to export schema DDL to the database. Simply remove (disable)hibernate.ddl-auto
or set it tovalidate
. Rely on Flyway or Liquibase.
Description: This application is an example of auto-creating and migrating two databases in MySQL using Flyway. In addition, each data source uses its own HikariCP connection pool. In case of MySQL, where a database is the same thing with schema, we create two databases,authorsdb
andbooksdb
.
Key points:
- for Maven, in
pom.xml
, add the Flyway dependency - remove (disable)
spring.jpa.hibernate.ddl-auto
or set it tovalidate
- in
application.properties
, configure the JDBC URL forbooksdb
asjdbc:mysql://localhost:3306/booksdb?createDatabaseIfNotExist=true
and forauthorsdb
asjdbc:mysql://localhost:3306/authorsdb?createDatabaseIfNotExist=true
- in
application.properties
, setspring.flyway.enabled=false
to disable default behavior - programmatically create two
DataSource
, one forbooksdb
and one forauthorsdb
- programmatically create two
FlywayDataSource
, one forbooksdb
and one forauthorsdb
- programmatically create two
EntityManagerFactory
, one forbooksdb
and one forauthorsdb
- for
booksdb
, place the migration SQLs files indb\migration\booksdb
- for
authorsdb
, place the migration SQLs files indb\migration\authorsdb
Description: This is a Spring Boot sample that exemplifies how thehi/lo
algorithm may cause issues when the database is used by external systems as well. Such systems can safely generate non-duplicated identifiers (e.g., for inserting new records) only if they know about thehi/lo
presence and its internal work. So, better rely onpooled
orpooled-lo
algorithm which doesn't cause such issues.
Key points:
- use the
SEQUENCE
generator type (e.g., in PostgreSQL) - configure the
hi/lo
algorithm as inAuthor.java
entity - insert a few records via
hi/lo
- insert a few records natively (this acts as an external system that relies on
NEXTVAL('hilo_sequence')
and is not aware ofhi/lo
presence and/or behavior)
Output sample: Running this application should result in the following error:ERROR: duplicate key value violates unique constraint "author_pkey"
Detail: Key (id)=(2) already exists.
Note: Rely onpooled-lo
orpooled
especially if, beside your application, external systems needs to insert rows in your tables. Don't rely onhi/lo
since, in such cases, it may cause errors resulted from generating duplicated identifiers.
Description: This is a Spring Boot example of using thepooled
algorithm. Thepooled
is an optimization ofhi/lo
. This algorithm fetched from the database the current sequence value as the top boundary identifier (the current sequence value is computed as the previous sequence value +increment_size
). This way, the application will use in-memory identifiers generated between the previous top boundary exclusive (aka, lowest boundary) and the current top boundary inclusive.
Key points:
- use the
SEQUENCE
generator type (e.g., in PostgreSQL) - configure the
pooled
algorithm as inAuthor.java
entity - insert a few records via
pooled
- insert a few records natively (this acts as an external system that relies on
NEXTVAL('hilo_sequence')
and is not aware ofpooled
presence and/or behavior)
Conclusion: In contrast to the classicalhi/lo
algorithm, the Hibernatepooled
algorithm doesn't cause issues to external systems that wants to interact with our tables. In other words, external systems can concurrently insert rows in the tables relying onpooled
algorithm. Nevertheless, old versions of Hibernate can raise exceptions caused byINSERT
statements triggered by external systems that uses the lowest boundary as identifier. This is a good reason to update to Hibernate latest versions (e.g., Hibernate 5.x), which have fixed this issue.
Note: Rely onpooled-lo
orpooled
especially if, beside your application, external systems needs to insert rows in your tables. Don't rely onhi/lo
since, in such cases, it may cause errors resulted from generating duplicated identifiers.
Description: This is a Spring Boot example of using thepooled-lo
algorithm. Thepooled-lo
is an optimization ofhi/lo
similar withpooled
. Only that, the strategy of this algorithm fetches from the database the current sequence value and use it as the in-memory lowest boundary identifier. The number of in-memory generated identifiers is equal toincrement_size
.
Key points:
- use the
SEQUENCE
generator type (e.g., in PostgreSQL) - configure the
pooled-lo
algorithm as inAuthor.java
entity - insert a few records via
pooled-lo
- insert a few records natively (this acts as an external system that relies on
NEXTVAL('hilo_sequence')
and is not aware ofpooled-lo
presence and/or behavior)
Description: This application uses Hibernate specific@BatchSize
at class/entity-level and collection-level. ConsiderAuthor
andBook
entities invovled in a bidirectional-lazy@OneToMany
association.
First use case fetches all
Author
entities via aSELECT
query. Further, calling thegetBooks()
method of the firstAuthor
entity will trigger anotherSELECT
query that initializes the collections of the first threeAuthor
entities returned by the previousSELECT
query. This is the effect of@BatchSize
atAuthor
's collection-level.Second use case fetches all
Book
entities via aSELECT
query. Further, calling thegetAuthor()
method of the firstBook
entity will trigger anotherSELECT
query that initializes the authors of the first threeBook
entities returned by the previousSELECT
query. This is the effect of@BatchSize
atAuthor
class-level.
Note: Fetching associated collections in the same query with their parent can be done viaJOIN FETCH
or entity graphs as well. Fetching children with their parents in the same query can be done viaJOIN FETCH
, entity graphs andJOIN
as well.
Key points:
Author
andBook
are in a lazy relationship (e.g.,@OneToMany
bidirectional relationship)Author
entity is annotated with@BatchSize(size = 3)
Author
's collection is annotated with@BatchSize(size = 3)
Note: In a nutshell,entity graphs (aka,fetch plans) is a feature introduced in JPA 2.1 that help us to improve the performance of loading entities. Mainly, we specify the entity’s related associations and basic fields that should be loaded in a singleSELECT
statement. We can define multipleentity graphs for the same entity andchain any number of entities and even usesub-graphs to create complexfetch plans. To override the currentFetchType
semantics there are properties that can be set:
Fetch Graph (default),javax.persistence.fetchgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated asFetchType.LAZY
regardless of the default/explicitFetchType
.
Load Graph,javax.persistence.loadgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated according to their specified or defaultFetchType
.
Nevertheless, the JPA specs doesn't apply in Hibernate for the basic (@Basic
) attributes.. More detailshere.
Description: This is a sample application of usingentity graphs in Spring Boot.
Key points:
- define two entities,
Author
andBook
, involved in a lazy bidirectional@OneToMany
association - in
Author
entity use the@NamedEntityGraph
to define theentity graph (e.g., load in a singleSELECT
the authors and the associatated books) - in
AuthorRepositry
rely on Spring@EntityGraph
annotation to indicate theentity graph defined at the previous step
Note: In a nutshell,entity graphs (aka,fetch plans) is a feature introduced in JPA 2.1 that help us to improve the performance of loading entities. Mainly, we specify the entity’s related associations and basic fields that should be loaded in a singleSELECT
statement. We can define multipleentity graphs for the same entity andchain any number of entities and even usesub-graphs to create complexfetch plans. To override the currentFetchType
semantics there are properties that can be set:
Fetch Graph (default),javax.persistence.fetchgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated asFetchType.LAZY
regardless of the default/explicitFetchType
.
Load Graph,javax.persistence.loadgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated according to their specified or defaultFetchType
.
Nevertheless, the JPA specs doesn't apply in Hibernate for the basic (@Basic
) attributes.. More detailshere.
Description: This is a sample application of usingentity sub-graphs in Spring Boot. There is one example based on@NamedSubgraph
and one based on the dot notation (.) in an ad-hocentity graph.
Key points:
- define three entities,
Author
,Book
andPublisher
(Author
andBook
are involved in a lazy bidirectional@OneToMany
relationship,Book
andPublisher
are also involved in a lazy bidirectional@OneToMany
relationship; betweenAuthor
andPublisher
there is no relationship)
Using@NamedSubgraph
- in
Author
entity define anentity graph via@NamedEntityGraph
; load the authors and the associatated books and use@NamedSubgraph
to define asub-graph for loading the publishers associated with these books - in
AuthorRepository
rely on Spring@EntityGraph
annotation to indicate theentity graph defined at the previous step
Using the dot notation (.)
- in
PublisherRepository
define an ad-hocentity graph that fetches all publishers with associated books, and further, the authors associated with these books (e.g.,@EntityGraph(attributePaths = {"books.author"})
.
Note: In a nutshell,entity graphs (aka,fetch plans) is a feature introduced in JPA 2.1 that help us to improve the performance of loading entities. Mainly, we specify the entity’s related associations and basic fields that should be loaded in a singleSELECT
statement. We can define multipleentity graphs for the same entity andchain any number of entities and even usesub-graphs to create complexfetch plans. To override the currentFetchType
semantics there are properties that can be set:
Fetch Graph (default),javax.persistence.fetchgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated asFetchType.LAZY
regardless of the default/explicitFetchType
.
Load Graph,javax.persistence.loadgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated according to their specified or defaultFetchType
.
Nevertheless, the JPA specs doesn't apply in Hibernate for the basic (@Basic
) attributes.. More detailshere.
Description: This is a sample application of defining ad-hocentity graphs in Spring Boot.
Key points:
- define two entities,
Author
andBook
, involved in a lazy bidirectional@OneToMany
relationship - theentity graph should load in a single
SELECT
the authors and the associatated books - in
AuthorRepository
rely on Spring@EntityGraph(attributePaths = {"books"})
annotation to indicate the ad-hocentity graph
Note: In a nutshell,entity graphs (aka,fetch plans) is a feature introduced in JPA 2.1 that help us to improve the performance of loading entities. Mainly, we specify the entity’s related associations and basic fields that should be loaded in a singleSELECT
statement. We can define multipleentity graphs for the same entity andchain any number of entities and even usesub-graphs to create complexfetch plans. To override the currentFetchType
semantics there are properties that can be set:
Fetch Graph (default),javax.persistence.fetchgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated asFetchType.LAZY
regardless of the default/explicitFetchType
.
Load Graph,javax.persistence.loadgraph
The attributes present inattributeNodes
are treated asFetchType.EAGER
. The remaining attributes are treated according to their specified or defaultFetchType
.
Nevertheless, the JPA specs doesn't apply in Hibernate for the basic (@Basic
) attributes. In other words, by default, attributes are annotated with@Basic
which rely on the default fetch policy. The default fetch policy isFetchType.EAGER
. These attributes are also loaded in case offetch graph even if they are not explicitly specified via@NamedAttributeNode
. Annotating the basic attributes that should not be fetched with@Basic(fetch = FetchType.LAZY)
it is not enough. Both,fetch graph andload graph will ignore these settings as long as we don't addbytecode enhancement as well.
The main drawback consists of the fact the these basic attributes are fetchedLAZY
by all other queries (e.g.,findById()
) not only by the queries using the entity graph, and most probably, you will not want this behavior.
Description: This is a sample application of usingentity graphs with@Basic
attributes in Spring Boot.
Key points:
- define two entities,
Author
andBook
, involved in a lazy bidirectional@OneToMany
association - in
Author
entity use the@NamedEntityGraph
to define theentity graph (e.g., load the authors names (only thename
basic attribute; ignore the rest) and the associatated books) - addbytecode enhancement
- annotate the basic attributes that should be ignored by theentity graph with
@Basic(fetch = FetchType.LAZY)
- in
AuthorRepository
rely on Spring@EntityGraph
annotation to indicate theentity graph defined at the previous step
Note: Spring Data built-in support for soft deletes is discussed inDATAJPA-307.
Description: This application is an example of implementing soft deletes in Spring Data style via a repository named,SoftDeleteRepository
.
Key points:
- define an
abstract
class,BaseEntity
, annotated with@MappedSuperclass
- in
BaseEntity
define a flag-field nameddeleted
(default this field tofalse
or in other words, not deleted) - every entity that wants to take advantage of soft deletes should extend the
BaseEntity
classs - write a
@NoRepositoryBean
namedSoftDeleteRepository
and extendJpaRepository
- override and implement the needed methods that provide the logic for soft deletes (check out the source code)
- repositories of entities should extend
SoftDeleteRepository
Description: This application is an example of how to implement concurrent table based queue viaSKIP_LOCKED
in MySQL 8.SKIP_LOCKED
can skip over locks achieved by other concurrent transactions, therefore is a great choice for implementing job queues. In this application, we run two concurrent transactions. The first transaction will lock the records with ids 1, 2 and 3. The second transaction will skip the records with ids 1, 2 and 3 and will lock the records with ids 4, 5 and 6.
Key points:
- define an entity that acts as a job queue (e.g., see the
Book
entity) - in
BookRepository
setup@Lock(LockModeType.PESSIMISTIC_WRITE)
- in
BookRepository
use@QueryHint
to setupjavax.persistence.lock.timeout
toSKIP_LOCKED
- rely on
org.hibernate.dialect.MySQL8Dialect
dialect - run two concurrent transactions to see the effect of
SKIP_LOCKED
Description: This application is an example of how to implement concurrent table based queue viaSKIP_LOCKED
in PostgreSQL.SKIP_LOCKED
can skip over locks achieved by other concurrent transactions, therefore is a great choice for implementing job queues. In this application, we run two concurrent transactions. The first transaction will lock the records with ids 1, 2 and 3. The second transaction will skip the records with ids 1, 2 and 3 and will lock the records with ids 4, 5 and 6.
Key points:
- define an entity that acts as a job queue (e.g., see the
Book
entity) - in
BookRepository
setup@Lock(LockModeType.PESSIMISTIC_WRITE)
- in
BookRepository
use@QueryHint
to setupjavax.persistence.lock.timeout
toSKIP_LOCKED
- rely on
org.hibernate.dialect.PostgreSQL95Dialect
dialect - run two concurrent transactions to see the effect of
SKIP_LOCKED
Description: This application is a sample of JPA Join Table inheritance strategy (JOINED
)
Key points:
- this inheritance strategy can be employed via
@Inheritance(strategy=InheritanceType.JOINED)
- all the classes in an inheritance hierarchy (a.k.a., subclasses) are represented via individual tables
- by default, subclass-tables contains a primary key column that acts as a foreign key as well - this foreign key references thebase class table primary key
- customizing this foreign key can be done by annotating the subclasses with
@PrimaryKeyJoinColumn
Description: This application is a sample of JPA Table-per-class inheritance strategy (TABLE_PER_CLASS
)
Key points:
- this inheritance strategy doesn't allow the usage of the
IDENTITY
generator - this inheritance strategy can be employed via
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
- all the classes in an inheritance hierarchy (a.k.a., subclasses) are represented via individual tables
- each subclass-table stores the columns inherited from the superclass-table (base class)
Description: This application is a sample of using the JPA@MappedSuperclass
.
Key points:
- thebase class is not an entity, it can be
abstract
, and is annotated with@MappedSuperclass
- subclasses of thebase class are mapped in tables that contains columns for the inherited attributes and for their own attibutes
- when thebase class doens't need to be an entity, the
@MappedSuperclass
is the proper alternative to the JPA table-per-class inheritance strategy
Note:Hibernate5Module is anadd-on module for Jackson JSON processor which handles Hibernate datatypes; and specifically aspects of lazy-loading.
Description: By default, in Spring Boot, the Open Session in View anti-pattern is enabled. Now, imagine a lazy relationship (e.g.,@OneToMany
) between two entities,Author
andBook
(an author has associated more books). Next, a REST controller endpoint fetches anAuthor
without the associatedBook
. But, the View (more precisely, Jackson), forces the lazy loading of the associatedBook
as well. Since OSIV will supply the already openedSession
, theProxy
initializations take place successfully.
Of course, the correct decision is to disable OSIV by setting it tofalse
, but this will not stop Jackson to try to force the lazy initialization of the associatedBook
entities. Running the code again will result in an exception of type:Could not write JSON: failed to lazily initialize a collection of role: com.bookstore.entity.Author.books, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.bookstore.entity.Author.books, could not initialize proxy - no Session.
Well, among the Hibernate5Module features we have support for dealing with this aspect of lazy loading and eliminate this exception. Even if OSIV will continue to be enabled (not recommended), Jackson will not use theSession
opened via OSIV.
Key points:
- for Maven, add the Hibernate5Module dependency in
pom.xml
- add a
@Bean
that returns an instance ofHibernate5Module
- annotate the
Author
bean with@JsonInclude(Include.NON_EMPTY)
to excludenull
or what is considered empty from the returned JSON
Note: The presence of Hibernate5Module instructs Jackson to initialize the lazy associations with default values (e.g., a lazy associated collection will be initialized withnull
). Hibernate5Module doesn't work for lazy loaded attributes. For such case considerthis item.
Description: View the prepared statement binding parameters viaprofileSQL=true
in MySQL.
Key points:
- in
application.properties
appendlogger=Slf4JLogger&profileSQL=true
to the JDBC URL (e.g.,jdbc:mysql://localhost:3306/bookstoredb?createDatabaseIfNotExist=true&logger=Slf4JLogger&profileSQL=true
)
Description: This application is an example of shuffling small results sets.DO NOT USE this technique for large results sets, since is extremely expensive.
Key points:
- write a JPQL
SELECT
query and append to itORDER BY RAND()
- each RDBMS support a function similar to
RAND()
(e.g., in PostgreSQL israndom()
)
Description: Commonly, deleting a parent and the associated children viaCascadeType.REMOVE
and/ororphanRemoval=true
involved several SQL statements (e.g., each child is deleted in a dedicatedDELETE
statement). When the number of entities is significant, this is far from being efficient, therefore other approaches should be employed.
ConsiderAuthor
andBook
in a bidirectional-lazy@OneToMany
association. This application exposes the best way to delete the parent(s) and the associated children in four scenarios listed below. These approaches relies onbulk deletions, therefore they are not useful if you want the deletions to take advantage of automatic optimistic locking mechanisms (e.g., via@Version
):
Best way to delete author(s) and the associated books viabulk deletions when:
- One
Author
is in Persistent Context, noBook
- More
Author
are in the Persistent Context, noBook
- One
Author
and the associatedBook
are in Persistent Context - No
Author
orBook
is in Persistent Context
Note: The most efficient way to delete all entities via abulk deletion can be done via the built-indeleteAllInBatch()
.
Description:Bulk operations (updates and deletes) are faster than batching, can benefit from indexing, but they have three main drawbacks:
- bulk updates/deletes may leave the Persistence Context in an outdated state (it's up to you to prevent this issue by flushing the Persistence Context before update/delete and close/clear it after the update/delete to avoid issues created by potentially unflushed or outdated entities)
- bulk updates/deletes don't benefit of automatic optimistic locking mechanisms (e.g.,
@Version
is ignored), therefore thelost updates are not prevented (it is advisable to signal these updates by explicitly incrementingversion
(if any is present)) - bulk deletes cannot take advantage of cascading removals (
CascadeType.REMOVE
) andorphanRemoval
This application provides examples ofbulk updates forAuthor
andBook
entities (betweenAuthor
andBook
there is a bidirectional lazy@OneToMany
association). Both,Author
andBook
, has aversion
field.
Description: As a rule of thumb, unidirectional@OneToMany
association is less efficient than the bidirectional@OneToMany
or the unidirectional@ManyToOne
associations. This application is a sample that exposes the DML statements generated for reads, writes and removal operations when the unidirectional@OneToMany
mapping is used.
Key points:
- regular unidirectional
@OneToMany
is less efficient than bidirectional@OneToMany
association - using
@OrderColumn
come with some optimizations for removal operations but is still less efficient than bidirectional@OneToMany
association - using
@JoinColumn
eliminates the junction table but is still less efficient than bidirectional@OneToMany
association - using
Set
instead ofList
or bidirectional@OneToMany
with@JoinColumn
relationship (e.g.,@ManyToOne @JoinColumn(name = "author_id", updatable = false, insertable = false)
) still performs worse than bidirectional@OneToMany
association
Description: This application is an example of using subqueries in JPQLWHERE
clause (you can easily use it inHAVING
clause as well).
Key points:
Keep in mind that subqueries and joins queries may or may not be semantically equivalent (joins may returns duplicates that can be removed viaDISTINCT
).
Even if the Execution Plan is specific to the database, historically speaking joins are faster than subqueries among different databases, but this is not a rule (e.g., the amount of data may significantly influence the results). Of course, do not conclude that subqueries are just a replacement for joins that doesn't deserve attention. Tuning subqueries can increases their performance as well, but this is an SQL wide topic. So, benchmark! Benchmark! Benchmark!
As a rule of thumb, prefer subqueries only if you cannot use joins, or if you can prove that they are faster than the alternative joins.
Note: Using SQL functions inSELECT
part (not inWHERE
part) of the query can be done ashere.
Description: Starting with JPA 2.1, a JPQL query can call SQL functions in theWHERE
part viafunction()
. This application is an example of calling the MySQL,concat_ws
function, but user defined (custom) functions can be used as well.
Key points:
- use JPA 2.1,
function()
Description: This application is an example of calling a MySQL stored procedure that returns a value (e.g., anInteger
).
Key points:
- rely on
@NamedStoredProcedureQuery
to shape the stored procedure in the entity - rely on
@Procedure
in repository
Description: This application is an example of calling a MySQL stored procedure that returns a result set. The application fetches entities (e.g.,List<Author>
) and DTO (e.g.,List<AuthorDto>
).
Key points:
- rely on
EntiyManager
since Spring Data@Procedure
will not work
Description: This application is an example of calling a MySQL stored procedure that returns a result set (entity or DTO) via a native query.
Key points:
- rely on a native call as
@Query(value = "{CALL FETCH_AUTHOR_BY_GENRE (:p_genre)}", nativeQuery = true)
Note: Most probably you'll like to process the result set viaBeanPropertyRowMapper
ashere. This is less verbose than the approach used here. Nevertheless, this approach is useful to understand how the result set looks like.
Description: This application is an example of calling a MySQL stored procedure that returns a result set viaJdbcTemplate
.
Key points:
- rely on
JdbcTemplate
andSimpleJdbcCall
Description: This application is an example of retrieving the database auto-generated primary keys.
Key points:
- JPA style, retrieve the auto-generated keys via
getId()
- JDBC style, retrieve the auto-generated keys via
JdbcTemplate
- JDBC style, retrieve the auto-generated keys via
SimpleJdbcInsert
Description: A Hibernate proxy can be useful when a child entity can be persisted with a reference to its parent (@ManyToOne
or@OneToOne
association). In such cases, fetching the parent entity from the database (execute theSELECT
statement) is a performance penalty and a pointless action. Hibernate can set the underlying foreign key value for an uninitialized proxy. This topic is discussedhere.
A proxy can be unproxied viaHibernate.unproxy()
. This method is available starting with Hibernate 5.2.10.
Key points:
- fetch a proxy via
JpaRepository#getOne()
- unproxy the fetched proxy via
Hibernate.unproxy()
Description: This application is an example of converting aBoolean
toYes/No strings viaAttributeConverter
. This kind of conversions are needed when we deal with legacy databases that connot be changed. In this case, the legacy database stores the booleans asYes/No.
Key points:
- implement a custom converter via
AttributeConverter
Note: The@ManyToOne
association maps exactly to the one-to-many table relationship. The underlying foreign key is under child-side control in unidirectional or bidirectional relationship.
Description: This application shows that using only@ManyToOne
is quite efficient. On the other hand, using only@OneToMany
is far away from being efficient. Always, prefer bidirectional@OneToMany
or unidirectional@ManyToOne
. Consider two entities,Author
andBook
in a unidirectional@ManyToOne
relationship.
Key points:
- Adding a new book is efficient
- Fetching all books of an author is efficient via a JPQL
- Pagination of books is efficient
- Remove a book is efficient
- Even if the fetched collection is not managed,dirty checking mechanism works as expected
Description: Trying to combineJOIN FETCH
/LEFT JOIN FETCH
andPageable
results in an exception of typeorg.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
. This application is a sample of how to avoid this exception.
Key points:
- use
countQuery
- use entity graph
Note: Fixing the above exception will lead to an warning of type HHH000104,firstResult / maxResults specified with collection fetch; applying in memory!
. If this warning is a performance issue, and most probably it is, then follow by readinghere.
Description: HHH000104 is a Hibernate warning that tell us that pagination of a result set is tacking place in memory. For example, consider theAuthor
andBook
entities in a lazy-bidirectional@OneToMany
association and the following query:
@Transactional
@Query(value = "SELECT a FROM Author a LEFT JOIN FETCH a.books WHERE a.genre = ?1",
countQuery = "SELECT COUNT(a) FROM Author a WHERE a.genre = ?1")
Page<Author> fetchWithBooksByGenre(String genre, Pageable pageable);
CallingfetchWithBooksByGenre()
works fine only that the following warning is signaled:HHH000104: firstResult / maxResults specified with collection fetch; applying in memory!
Obviously, having pagination in memory cannot be good from performance perspective. This application implement a solution for moving pagination at database-level.
Key points:
- use three or two JPQL queries for fetching
Page
of entities in read-write or read-only mode - use two JPQL queries for fetching
Slice
orList
of entities in read-write or read-only mode
Description: This application is meant to reveal what is the difference between@Transactional(readOnly = false)
and@Transactional(readOnly = true)
. In a nuthsell,readOnly = false
(default) fetches entites inread-write mode (managed). Before Spring 5.1,readOnly = true
just setFlushType.MANUAL/NEVER
, therefore the automaticdirty checking mechanism will not take action since there is no flush. In other words, Hibernate keep in the Persistent Context the fetched entities and the hydrated (loaded) state. By comparing the entity state with the hydrated state, thedirty checking mechanism can decide to triggerUPDATE
statements in our behalf. But, thedirty checking mechanism take place at flush time, therefore, without a flush, the hydrated state is kept in Persistent Context for nothing, representing a performance penalty. Starting with Spring 5.1, theread-only mode is propagated to Hibernate, therefore the hydrated state is discarded immediately after loading the entities. Even if theread-only mode discards the hydrated state the entities are still loaded in the Persistent Context, therefore, forread-only data, relying on DTO (Spring projection) is better.
Key points:
readOnly = false
load data inread-write mode (managed)readOnly = true
discard the hydrated state (starting with Spring 5.1)
Description: This application is an example of getting the current database transaction id in MySQL. Only read-write database transactions gets an id in MySQL. Every database has a specific query for getting the transaction id.Here it is a list of these queries.
Key points:
- rely on the following query,
SELECT tx.trx_id FROM information_schema.innodb_trx tx WHERE tx.trx_mysql_thread_id = connection_id()
Description: This application is a sample of inspecting the Persistent Context content viaorg.hibernate.engine.spi.PersistenceContext
.
Key points:
- get the current Persistent Context via Hibernate
SharedSessionContractImplementor
- rely on
PersistenceContext
API
Description: This application is an example of using the Hibernate SPI,org.hibernate.integrator.spi.Integrator
for extracting tables metadata.
Key points:
- implement
org.hibernate.integrator.spi.Integrator
and overrideintegrate()
method to returnmetadata.getDatabase()
- register this
Integrator
viaLocalContainerEntityManagerFactoryBean
Description: This application is an example of mapping the JPA@ManyToOne
relationship to a SQL query via the Hibernate@JoinFormula
annotation. We start with two entities,Author
andBook
, involved in a unidirectional@ManyToOne
relationship. Each book has a price. While we fetch a book by id (let's call it bookA
), we want to fetch another bookB
of the same author whose price is the next smaller price in comparison with bookA
price.
Key points:
- fetching the book
B
is done via@JoinFormula
Description: This application is an example of fetching a read-only MySQL database view in a JPA immutable entity.
Key points:
- the database view is available in
data-mysql.sql
file - the entity used to map the database view is
GenreAndTitleView.java
Description: This application is an example of updating, inserting and deleting data in a MySQL database view. Every update/insert/delete will automatically update the contents of the underlying table(s).
Key points:
- the database views are available in
data-mysql.sql
file - respectMySQL requirements for updatable and insertable database views
Description: This application is an example of preventing inserts/updates of a MySQL view that are not visible through this view viaWITH CHECK OPTION
. In other words, whenever you insert or update a row of the base tables through a view, MySQL ensures that the this operation is conformed with the definition of the view.
Key points:
- add
WITH CHECK OPTION
to the view - this application will throw an exception of type
java.sql.SQLException: CHECK OPTION failed 'bookstoredb.author_anthology_view
Description: This application is an example of assigning a database temporary sequence of values to rows via the window function,ROW_NUMBER()
. This window function is available in almost all databases, and starting with version 8.x is available in MySQL as well.
Key points:
- commonly, you don't need to fetch in the result set the temporary sequence of values produced by
ROW_NUMBER()
(you will use it internally, in the query, usually in theWHERE
clause and CTEs), but, this time, let's write a Spring projection (DTO) that contains a getter for the column generated byROW_NUMBER
as well - write several native querys relying on
ROW_NUMBER()
window function
Description: This application is an example of finding top N rows of every group.
Key points:
- write a native query relying on
ROW_NUMBER()
window function
Description: This application is an example of usingROW_NUMBER()
(andCOUNT(*) OVER()
for counting all elements) window function to implement pagination.
Key points:
- use a native query relying on
ROW_NUMBER()
- we don't return a page as
Page
orSlice
, we return it asList
, thereforePageable
is not used
Description: This application is an example of fixing the case when@Transactional
annotation is ignored. Most of the time, this annotation is ignored in the following scenarios:
@Transactional
was added to aprivate
,protected
orpackage-protected
method@Transactional
was added to a method defined in the same class where it is invoked
Key points:
- write a helper service and move the
@Transactional
methods there - ensure that these methods are declared as
public
- call
@Transactional
methods from other services
Description: This is a Spring Boot example of using thehi/lo
algorithm and a custom implementation ofSequenceStyleGenerator
for generating custom sequence IDs (e.g,A-0000000001
,A-0000000002
, ...).
Key points:
- extend
SequenceStyleGenerator
and override theconfigure()
andgenerate()
methods - set this generator in entities
Description: This application is an example of mappingClob
andBlob
asbyte[]
andString
.
Key points:
- this is vey easy to use but the application doesn't take advantage of JDBC driver LOB-specific optimizations
Description: This application is an example of mapping to JDBC'sLOB
locatorsClob
andBlob
.
Key points:
- this takes advantage of JDBC driver LOB-specific optimizations
Description: This application is a sample of fetching a certain subclass from aSINGLE_TABLE
inheritance hierarchy. This is useful when the dedicated repository of the subclass doesn't automatically add in theWHERE
clause adtype
based condition for fetching only the needed subclass.
Key points:
- explicitly add in the
WHERE
clause aTYPE
check
Description: This is a SpringBoot application that defines a@ManyToOne
relationship that doesn't reference a primary key column. It references a Hibernate@NaturalId
column.
Key points:
- rely on
@JoinColumn(referencedColumnName = "natural_id_column")
Description: This application is an example of implementing an advanced search viaSpecification
API. Mainly, you can give the search filters to a genericSpecification
and fetch the result set. Pagination is supported as well. You can chain expressions via logicalAND
andOR
to create compound filters. Nevertheless, there is room for extensions to add brackets support (e.g.,(x AND y) OR (x AND z)
), more operations, conditions parser and so on and forth.
Key points:
- write a generic
Specification
Description: This application contains two examples of how to defineJOIN
inSpecification
to emulate JPQL join-fetch operations.
Key points:
- the first approach trigger two
SELECT
statements and the pagination is done in memory (very bad!) - the second approach trigger three
SELECT
statements but the pagination is done in the database - in both approaches the
JOIN
is defined in aSpecification
implementation
Note: You may also like to read the recipe,"How To Enrich DTO With Virtual Properties Via Spring Projections"
Description: Fetch only the needed data from the database via Spring Data Projections (DTO). The projection interface is defined as astatic
interface (can be non-static
as well) in the repository interface.
Key points:
- write an interface (projection) containing getters only for the columns that should be fetched from the database
- write the proper query returning a
List<projection>
- if is applicable, limit the number of returned rows (e.g., via
LIMIT
) - here, we can use query builder mechanism built into Spring Data repository infrastructure
Note: Using projections is not limited to use query builder mechanism built into Spring Data repository infrastructure. We can fetch projections via JPQL or native queries as well. For example, in thisapplication we use a JPQL.
Output example (select first 2 rows; select only "name" and "age"):
Description: Consider an entity namedReview
. This entity defines three@ManyToOne
relationships toBook
,Article
andMagazine
. A review can be associated with either a book, a magazine or an article. To validate this constraint, we can rely onBean Validation as in this application.
Key points:
- rely on Bean Validation to validate that only one association is non-
null
- expose the constraint via a custom annotation (
@JustOneOfMany
) added at class-level to theReview
entity - for preventing native query to break our constraint add the validation at database level as well (e.g., in MySQL add a
TRIGGER
)
Description: This application usesEnumType.ORDINAL
andEnumType.STRING
for mapping Javaenum
type to database. As a rule of thumb, strive to keep the data types as small as possible (e.g., forEnumType.ORDINAL
useTINYINT/SMALLINT
, while forEnumType.STRING
useVARCHAR(max_needed_bytes)
). Relying onEnumType.ORDINAL
should be more efficient but is less expressive thanEnumType.STRING
.
Key points:
- strive for smallest data types (e.g., for
EnumType.ORDINAL
set@Column(columnDefinition = "TINYINT")
)
Description: This application maps a Javaenum
viaAttributeConverter
. In other words, it maps theenum
valuesHORROR
,ANTHOLOGY
andHISTORY
to the integers1
,2
and3
and viceversa. This allows us to set the column type asTINYINT/SMALLINT
which is less space-consuming thanVARCHAR(9)
needed in this case.
Key points:
- define a custom
AttributeConverter
- annotate with
@Converter
the corresponding entity field
Description: This application maps a Javaenum
type to PostgreSQLenum
type.
Key points:
- define a custom Hibernate
EnumType
- register this custom
EnumType
viapackage-info.java
- annotate the corresponding entity field
@Type
Description: This application maps a Javaenum
type to PostgreSQLenum
type viaHibernate Types library.
Key points:
- install Hibernate Types library via
pom.xml
- use
@TypeDef
to specify the needed type class - annotate the corresponding entity field with
@Type
Description:Hibernate Types is a library of extra types not supported by Hibernate Core by default. This is a Spring Boot application that uses this library to persist JSON data (JSON JavaObject
) in a MySQLjson
column and for querying JSON data from the MySQLjson
column to JSON JavaObject
. Updates are supported as well.
Key points:
- for Maven, add Hibernate Types as a dependency in
pom.xml
- in entity use
@TypeDef
to maptypeClass
toJsonStringType
Description:Hibernate Types is a library of extra types not supported by Hibernate Core by default. This is a Spring Boot application that uses this library to persist JSON data (JSON JavaObject
) in a PostgreSQLjson
column and for querying JSON data from the PostgreSQLjson
column to JSON JavaObject
. Updates are supported as well.
Key points:
- for Maven, add Hibernate Types as a dependency in
pom.xml
- in entity use
@TypeDef
to maptypeClass
toJsonBinaryType
Description: This application is a sample of howOPTIMISTIC_FORCE_INCREMENT
works in MySQL. This is useful when you want to increment the version of the locked entity even if this entity was not modified. ViaOPTIMISTIC_FORCE_INCREMENT
the version is updated (incremented) at the end of the currently running transaction.
Key points:
- use a root entity,
Chapter
(which uses@Version
) - several editors load a chapter and perform modifications mapped via the
Modification
entity - between
Modification
(child-side) andChapter
(parent-side) there is a lazy unidirectional@ManyToOne
association - for each modification, Hibernate will trigger an
INSERT
statement against themodification
table, therefore thechapter
table will not be modified by editors - but,
Chapter
entity version is needed to ensure that modifications are applied sequentially (the author and editor are notified if a modificaton was added since the chapter copy was loaded) - the
version
is forcibly increased at each modification (this is materialized in anUPDATE
triggered against thechapter
table at the end of the currently running transaction) - set
OPTIMISTIC_FORCE_INCREMENT
in the corresponding repository - rely on two concurrent transactions to shape the scenario that will lead to an exception of type
ObjectOptimisticLockingFailureException
Description: This application is a sample of howPESSIMISTIC_FORCE_INCREMENT
works in MySQL. This is useful when you want to increment the version of the locked entity even if this entity was not modified. ViaPESSIMISTIC_FORCE_INCREMENT
the version is updated (incremented) immediately (the entity version update is guaranteed to succeed immediately after acquiring the row-level lock). The incrementation takes place before the entity is returned to the data access layer.
Key points:
- use a root entity,
Chapter
(which uses@Version
) - several editors load a chapter and perform modifications mapped via the
Modification
entity - between
Modification
(child-side) andChapter
(parent-side) there is a lazy unidirectional@ManyToOne
association - for each modification, Hibernate will trigger an
INSERT
statement against themodification
table, therefore thechapter
table will not be modified by editors - but,
Chapter
entityversion
is needed to ensure that modifications are applied sequentially (each editor is notified if a modificaton was added since his chapter copy was loaded and he must re-load the chapter) - the
version
is forcibly increased at each modification (this is materialized in anUPDATE
triggered against thechapter
table immediately after aquiring the row-level lock) - set
PESSIMISTIC_FORCE_INCREMENT
in the corresponding repository - rely on two concurrent transactions to shape two scenarios: one that will lead to an exception of type
OptimisticLockException
and one that will lead toQueryTimeoutException
Note: Pay attention to the MySQL dialect:MySQL5Dialect
(MyISAM) doesn't support row-level locking,MySQL5InnoDBDialect
(InnoDB) acquires row-level lock viaFOR UPDATE
(timeout can be set),MySQL8Dialect
(InnoDB) acquires row-level lock viaFOR UPDATE NOWAIT
.
Description: This application is an example of usingPESSIMISTIC_READ
andPESSIMISTIC_WRITE
in MySQL. In a nutshell, each database system defines its own syntax for acquiring shared and exclusive locks and not all databases support both types of locks. Depending onDialect
, the syntax can vary for the same database as well (Hibernate relies onDialect
for chosing the proper syntax). In MySQL,MySQL5Dialect
doesn't support locking, while InnoDB engine (MySQL5InnoDBDialect
andMySQL8Dialect
) supports shared and exclusive locks as expected.
Key points:
- rely on
@Lock(LockModeType.PESSIMISTIC_READ)
and@Lock(LockModeType.PESSIMISTIC_WRITE)
on query-level - for testing, use
TransactionTemplate
to trigger two concurrent transactions that read and write the same row
Description: This application is an example of triggeringUPDATE
,INSERT
andDELETE
operations in the context ofPESSIMISTIC_WRITE
locking against MySQL. WhileUPDATE
andDELETE
are blocked until the exclusive lock is released,INSERT
depends on the transaction isolation level. Typically, even with exclusive locks, inserts are possible (e.g., in PostgreSQL). In MySQL, for the default isolation level,REPEATABLE READ
, inserts are prevented against a range of locked entries, but, if we switch toREAD_COMMITTED
, then MySQL acts as PostgreSQL as well.
Key points:
- startTransaction A and trigger a
SELECT
withPESSIMISTIC_WRITE
to acquire an exclusive lock - start a concurrentTransaction B that triggers an
UPDATE
,INSERT
orDELETE
on the rows locked byTransaction A - in case of
UPDATE
,DELETE
andINSERT
+REPEATABLE_READ
,Transaction B is blocked until it timeouts orTransaction A releases the exclusive lock - in case of
INSERT
+READ_COMMITTED
,Transaction B can insert in the range of rows locked byTransaction A even ifTransaction A is holding an exclusive lock on this range
Note: Do not test transaction timeout viaThread.sleep()
! This is not working! Rely on two transactions and exclusive locks or even better rely on SQL sleep functions (e.g., MySQL,SELECT SLEEP(n)
seconds, PostgreSQL,SELECT PG_SLEEP(n)
seconds). Most RDBMS supports a sleep function flavor.
Description: This application contains several approaches for setting a timeout period for a transaction or query. The timeout is signaled by a specific timeout exception (e.g.,.QueryTimeoutException
). After timeout, the transaction is rolled back. You can see this in the database (visually or query) and on log via a message of type:Initiating transaction rollback; Rolling back JPA transaction on EntityManager [SessionImpl(... <open>)]
.
Key points:
- set global transaction timeout via
spring.transaction.default-timeout
in seconds (see,application.properties
) - set transaction timeout at method-level or class-level via
@Transactional(timeout = n)
in seconds - set query timeout via JPA
javax.persistence.query.timeout
hint in milliseconds - set query timeout via Hibrenate
org.hibernate.timeout
hint in seconds
Note: If you are usingTransactionTemplate
then the timeout can be set viaTransactionTemplate.setTimeout(n)
in seconds.
Description: This application is a proof of concept of how to define a composite key via@Embeddable
and@EmbeddedId
. This application uses two entities,Author
andBook
involved in a lazy bidirectional@OneToMany
association. The identifier ofAuthor
is composed byname
andage
viaAuthorId
class. The identifier ofBook
is just a regular auto-generated numeric value.
Key points:
- the composite key class (e.g.,
AuthorId
) ispublic
- the composite key class must implement
Serializable
- the composite key must define
equals()
andhashCode()
- the composite key must define a no-arguments constructor
Description: This application is a proof of concept of how to define a composite key via@IdClass
. This application uses two entities,Author
andBook
involved in a lazy bidirectional@OneToMany
association. The identifier ofAuthor
is composed byname
andage
viaAuthorId
class. The identifier ofBook
is just a typical auto-generated numeric value.
Key points:
- the composite key class (e.g.,
AuthorId
) ispublic
- the composite key class must implement
Serializable
- the composite key must define
equals()
andhashCode()
- the composite key must define a no-arguments constructor
Note: The@IdClass
can be useful when we cannot modify the compsite key class. Otherwise, rely on@Embeddable
.
Description: This application is a proof of concept of how to define a relationship in an@Embeddable
composite key. The composite key isAuthorId
and it belongs to theAuthor
class.
Key points:
- the composite key class (e.g.,
AuthorId
) ispublic
- the composite key class must implement
Serializable
- the composite key must define
equals()
andhashCode()
- the composite key must define a no-arguments constructor
Description: This is a SpringBoot application that loads multiple entities by id via a@Query
based on theIN
operator and via the Hibernate 5MultiIdentifierLoadAccess
interface.
Key points:
- for using the
IN
operator in a@Query
simply add the query in the proper repository - for using Hibernate 5
MultiIdentifierLoadAccess
in Spring Data style provide the proper implementation - among its advantages, the
MultiIdentifierLoadAccess
implementation allows us to load entities by multiple ids in batches and by inspecting or not the current Persistent Context (by default, the Persistent Context is not inspected to see if the entities are already loaded or not)
Description: This application is a sample of fetching all attributes of an entity (Author
) as a Spring projection (DTO). Commonly, a DTO contains a subset of attributes, but, sometimes we need to fetch the whole entity as a DTO. In such cases, we have to pay attention to the chosen approach. Choosing wisely can spare us from performance penalties.
Key points:
- fetching the result set as a
List<Object[]>
orList<AuthorDto>
via a JPQL of typeSELECT a FROM Author a
WILL fetch the result set as entities in Persistent Context as well - avoid this approach - fetching the result set as a
List<Object[]>
orList<AuthorDto>
via a JPQL of typeSELECT a.id AS id, a.name AS name, ... FROM Author a
willNOT fetch the result set in Persistent Context - this is efficient - fetching the result set as a
List<Object[]>
orList<AuthorDto>
via a native SQL of typeSELECT id, name, age, ... FROM author
willNOT fetch the result set in Persistent Context - but, this approach is pretty slow - fetching the result set as a
List<Object[]>
via Spring Data query builder mechanismWILL fetch the result set in Persistent Context - avoid this approach - fetching the result set as a
List<AuthorDto>
via Spring Data query builder mechanism willNOT fetch the result set in Persistent Context - fetching the result set asread-only entitites (e.g., via the built-in
findAll()
method) should be considered after JPQL with explicit list of columns to be fetched and query builder mechanism
Description: This application fetches a Spring projection including the@ManyToOne
association via different approaches. It can be easily adapted for@OneToOne
association as well.
Key points:
- fetching raw data is the fastest approach
Description: This application inspect the Persistent Context content during fetching Spring projections that includes collections of associations. In this case, we focus on a@OneToMany
association. Mainly, we want to fetch only some attributes from the parent-side and some attributes from the child-side.
Description: This application is a sample of reusing an interface-based Spring projection. This is useful to avoid defining multiple interface-based Spring projections in order to cover a range of queries that fetches different subsets of fields.
Key points:
- define an interface-based Spring projection containing getters for the wider case
- rely on class-level
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
annotation to avoid serialization of default fields (e.g., fields that are not available in the current projection and arenull
- these fields haven't been fetched in the current query) - this is useful to Jackson that will not serialize in the resulted JSON the missing fields (e.g.,
null
fields)
Description: This application is a sample of using dynamic Spring projections.
Key points:
- declare query-methods in a generic manner (e.g.,
<T> List<T> findByGenre(String genre, Class<T> type);
)
Description: This application is a sample of batching inserts viaEntityManager
in MySQL. This way you can easily control theflush()
andclear()
cycles of the Persistence Context (1st Level Cache) inside the current transaction. This is not possible via Spring Boot,saveAll(Iterable<S> entities)
, since this method executes a single flush per transaction. Another advantage is that you can callpersist()
instead ofmerge()
- this is used behind the scene by the SpringBootsaveAll(Iterable<S> entities)
andsave(S entity)
.
Moreover, this example commits the database transaction after each batch excecution. This way we avoid long-running transactions and, in case of a failure, we rollback only the failed batch and don't lose the previous batches. For each batch, the Persistent Context is flushed and cleared, therefore we maintain a thin Persistent Context. This way the code is not prone to memory errors and performance penalties caused by slow flushes.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since MySQL
IDENTITY
will cause insert batching to be disabled - in your DAO layer, flush and clear the Persistence Context from time to time (e.g. for each batch); this way you avoid to "overwhelm" the Persistence Context
- in your DAO layer, commit the database transaction after each batch execution
- if is not needed, then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: This is a Spring Boot application that reads a relatively big JSON file (200000+ lines) and inserts its content in MySQL via batching usingForkJoinPool
,JdbcTemplate
and HikariCP.
Key points:
- using MySQL,
json
type - read the file content into a
List
- the list is halved and subtasks are created until the list size is small than the batch size (e.g., by default smaller than 30)
- for MySQL, in application.properties, you may want to attach to the JDBC URL the following:
rewriteBatchedStatements=true
-> this setting will force sending the batched statements in a single request;cachePrepStmts=true
-> enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disableduseServerPrepStmts=true
-> this way you switch to server-side prepared statements (may lead to signnificant performance boost); moreover, you avoid thePreparedStatement
to be emulated at the JDBC Driver level;- we use the following JDBC URL settings:
...?cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true&createDatabaseIfNotExist=true
- Note: Older MySQL versions will not tolerate well to have toghether rewritting and server-side prepared statement activated. For being sure that these statements still valid please check the notes of the Connector/J that you are using
- set the HikariCP to provide a number of database connections that ensure that the database achives a minimum context switching (e.g., 2 * number of CPU cores)
- this application uses
StopWatch
to measure the time needed to transfer the file into the database - in order to run the application you have to unzip the
citylots.zip
in the current location; this is the big JSON file collected from Internet; - if you want to see details about the batch process simply activate the
DatasourceProxyBeanPostProcessor.java
component by uncomment the line,// @Component
; This is needed because this application relies on DataSource-Proxy (for details, see the followingitem)
Description: This application is a sample of usingCompletableFuture
for batching inserts. ThisCompletableFuture
uses anExecutor
that has the number of threads equal with the number of your computer cores. Usage is in Spring style.
Description: Let's suppose that we have a one-to-many relationship betweenAuthor
andBook
entities. When we save an author, we save his books as well thanks to cascading all/persist. We want to create a bunch of authors with books and save them in the database (e.g., a MySQL database) using the batch technique. By default, this will result in batching each author and the books per author (one batch for the author and one batch for the books, another batch for the author and another batch for the books, and so on). In order to batch authors and books, we need toorder inserts as in this application.
Moreover, this example commits the database transaction after each batch excecution. This way we avoid long-running transactions and, in case of a failure, we rollback only the failed batch and don't lose the previous batches. For each batch, the Persistent Context is flushed and cleared, therefore we maintain a thin Persistent Context. This way the code is not prone to memory errors and performance penalties caused by slow flushes.
Key points:
- beside all setting specific to batching inserts in MySQL, we need to set up in
application.properties
the following property:spring.jpa.properties.hibernate.order_inserts=true
- in your DAO layer, commit the database transaction after each batch execution
Example without ordered inserts:
Description: Batch inserts (in MySQL) in Spring Boot style. This example commits the database transaction after each batch excecution. This way we avoid long-running transactions and, in case of a failure, we rollback only the failed batch and don't lose the previous batches.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.jdbc.batch_size
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since the Hibernate
IDENTITY
will cause insert batching to be disabled - in your DAO layer, commit the database transaction after each batch execution
- if is not needed then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: This application is an example of using HibernateIN
cluase parameter padding. This way we can reduce the number of Execution Plans. Mainly, Hibernate is padding parameters as follows:
- for 3 and 4 parameters -> it uses 4 bind parameters (2^2)
- for 5, 6, 7 and 8 parameters -> it uses 8 bind parameters (2^3)
- for 9, 10, 11, 12, 13, 14, 15 and 16 parameters -> it uses 16 parameters (2^4)
- ...
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.query.in_clause_parameter_padding=true
Description: Fetch only the needed data from the database via Spring Data Projections (DTO). In this case, via class-based projections.
Key points:
- write an class (projection) containing a constructor, getters, setters,
equals()
andhashCode()
only for the columns that should be fetched from the database - write the proper query returning a
List<projection>
- if it is applicable, limit the number of returned rows (e.g., via
LIMIT
) - in this example, we can use query builder mechanism built into Spring Data repository infrastructure
Note: Using projections is not limited to use query builder mechanism built into Spring Data repository infrastructure. We can fetch projections via JPQL or native queries as well. For example, in thisapplication we use a JPQL.
Output example (select first 2 rows; select only "name" and "age"):
Description: Batch inserts via Hibernate session-level batching (Hibernate 5.2 or higher) in MySQL. This example commits the database transaction after each batch excecution. This way we avoid long-running transactions and, in case of a failure, we rollback only the failed batch and don't lose the previous batches. For each batch, the Persistent Context is flushed and cleared, therefore we maintain a thin Persistent Context. This way the code is not prone to memory errors and performance penalties caused by slow flushes.
Key points:
- in
application.properties
setspring.jpa.properties.hibernate.generate_statistics
(just to check that batching is working) - in
application.properties
set JDBC URL withrewriteBatchedStatements=true
(optimization for MySQL) - in
application.properties
set JDBC URL withcachePrepStmts=true
(enable caching and is useful if you decide to setprepStmtCacheSize
,prepStmtCacheSqlLimit
, etc as well; without this setting the cache is disabled) - in
application.properties
set JDBC URL withuseServerPrepStmts=true
(this way you switch to server-side prepared statements (may lead to signnificant performance boost)) - in case of using a parent-child relationship with cascade persist (e.g. one-to-many, many-to-many) then consider to set up
spring.jpa.properties.hibernate.order_inserts=true
to optimize the batching by ordering inserts - in entity, use theassigned generator since MySQL
IDENTITY
will cause insert batching to be disabled - the Hibernate
Session
is obtained by un-wrapping it viaEntityManager#unwrap(Session.class)
- the batching size is set via
Session#setJdbcBatchSize(Integer size)
and get viaSession#getJdbcBatchSize()
- in your DAO layer, commit the database transaction after each batch execution
- if is not needed, then ensure that Second Level Cache is disabled via
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
Description: This application highlights the difference betweeen loading entities inread-write vs.read-only mode. If you plan to modify the entities in a future Persistent Context then fetch them asread-only in the current Persistent Context.
Key points:
- in the current Persistent Context, fetch entities inread-only mode
- modifiy the entities in the current Persistent Context or in detached state (the potential modifications done in the current Persistent Context will not be propagated to the database at flush time)
- in a subsequent Persistent Context, merge the detached entity and propagate changes to the database
Note: If you never plan to modify the fetched result set then use DTO (e.g., Spring projection), notread-only entities.
Note: Domain events should be used with extra-caution! The best practices for using them are revealed in my book,Spring Boot Persistence Best Practices.
Description: Starting with Spring Data Ingalls release publishing domain events by aggregate roots becomes easier. Entities managed by repositories are aggregate roots. In a Domain-Driven Design application, these aggregate roots usually publish domain events. Spring Data provides an annotation@DomainEvents
you can use on a method of your aggregate root to make that publication as easy as possible. A method annotated with@DomainEvents
is automatically invoked by Spring Data whenever an entity is saved using the right repository. Moreover, Spring Data provides the@AfterDomainEventsPublication
annotation to indicate the method that should be automatically called for clearing events after publication. Spring Data Commons comes with a convenient template base class (AbstractAggregateRoot
) to help to register domain events and is using the publication mechanism implied by@DomainEvents
and@AfterDomainEventsPublication
. The events are registered by calling theAbstractAggregateRoot.registerEvent()
method. The registered domain events are published if we call one of thesave methods (e.g.,save()
) of the Spring Data repository and cleared after publication.
This is a sample application that relies onAbstractAggregateRoot
and itsregisterEvent()
method. We have two entities,Book
andBookReview
involved in a lazy-bidirectional@OneToMany
association. A new book review is saved inCHECK
status and aCheckReviewEvent
is published. This event handler is responsible to check the review grammar, content, etc and switch the review status fromCHECK
toACCEPT
orREJECT
and propagate the new status to the database. So, this event is registered before saving the book review inCHECK
status and is published automatically after we call theBookReviewRepository.save()
method. After publication, the event is cleared.
Key points:
- the entity (aggregate root) that publish events should extend
AbstractAggregateRoot
and provide a method for registering events - here, we register a single event (
CheckReviewEvent
), but more can be registered - event handling take place is
CheckReviewEventHandler
in an asynchronous manner via@Async
Description: This application is an example of testing the Hibernate Query Plan Cache (QPC). Hibernate QPC is enabled by default and, for entity queries (JPQL and Criteria API), the QPC has a size of 2048, while for native queries it has a size of 128. Pay attention to alter these values to accommodate all queriesexecuted by your application. If the number of exectued queries is higher than the QPC size (especially for entity queries) then you will start to experiment performance penalties caused by entity compilation time added for each query execution.
In this application, you can adjust the QPC size inapplication.properties
. Mainly, there are 2 JPQL queries and a QPC of size 2. Switching from size 2 to size 1 will cause the compilation of one JPQL query at each execution. Measuring the times for 5000 executions using a QPC of size 2, respectively 1 reveals the importance of QPC in terms of time.
Key points:
- for JPQL and Criteria API you can set the QPC via
hibernate.query.plan_cache_max_size
- for native queries you can set the QPC via
hibernate.query.plan_parameter_metadata_max_size
Description: This is a SpringBoot application that enables Hibernate Second Level Cache and EhCache provider. It contains an example of caching entities and an example of caching a query result.
Key points:
- enable Second Level Cache (
EhCache
) - rely on
@Cache
- rely on JPA hint
HINT_CACHEABLE
Description: This is a SpringBoot application representing a kickoff application for Spring Boot caching andEhCache
.
Key points:
- setup
EhCache
- rely on Spring caching annotations
Note: If you want to rely on the{EntityName}.{RepositoryMethodName}
naming convention for simply creating in the repository interface methods with the same name as of native named query then skip this application andcheck this one.
Description: This is a sample application of usingSqlResultSetMapping
,NamedNativeQuery
andEntityResult
for fetching single entity and multiple entities asList<Object[]>
.
Key points:
- use
SqlResultSetMapping
,NamedNativeQuery
andEntityResult
Description: This is a SpringBoot application that loads multiple entities by id via a@Query
based on theIN
operator and viaSpecification
.
Key points:
- for using the
IN
operator in a@Query
simply add the query in the proper repository - for using a
Specification
rely onjavax.persistence.criteria.Root.in()
Description: Fetching moreread-only data than needed is prone to performance penalties. Using DTO allows us to extract only the needed data. Sometimes, we need to fetch a DTO made of a subset of properties (columns) from a parent-child association. For such cases, we can use SQLJOIN
that can pick up the desired columns from the involved tables. But,JOIN
returns anList<Object[]>
and most probably you will need to represent it as aList<ParentDto>
, where aParentDto
instance has aList<ChildDto>
. For such cases, we can rely on a custom HibernateResultTransformer
. This application is a sample of writing a customResultTransformer
.
Key points:
- implement the
ResultTransformer
interface
Description: Is a common scenario to have a bigList
and to need to chunk it in multiple smallerList
of given size. For example, if we want to employee a concurrent batch implementation we need to give to each thread a sublist of items. Chunking a list can be done via Google Guava,Lists.partition(List list, int size)
method or Apache Commons Collections,ListUtils.partition(List list, int size)
method. But, it can be implemented in plain Java as well. This application exposes 6 ways to do it. The trade-off is between the speed of implementation and speed of execution. For example, while the implementation relying on grouping collector is not performing very well, it is quite simple and fast to write it.
Key points:
- the fastest execution is provided by
Chunk.java
class which relies on the built-inList.subList()
method
Time-performance trend graphic for chunking 500, 1_000_000, 10_000_000 and 20_000_000 items in lists of 5 items:
Description: Consider theBook
andChapter
entities. A book has a maximum accepted number of pages (book_pages
) and the author should not exceed this number. When a chapter is ready for review, the author is submitting it. At this point, the publisher should check that the currently total number of pages doesn't exceed the allowedbook_pages
:
This kind of checks or constraints are easy to implement via database triggers. This application relies on a MySQL trigger to empower our complex contraint (check_book_pages
).
Key points:
- define a MySQL trigger that run after each insert (if you want to run it after each update as well then extract the trigger logic into a function and call it from two triggers - this is specific to MySQL, while is PostgreSQL we have
AFTER INSERT OR AFTER UPDATE
)
Description: This application is an example of using Spring Data Query By Example (QBE) to check if a transient entity exists in the database. Consider theBook
entity and a Spring controller that exposes an endpoint as:public String checkBook(@Validated @ModelAttribute Book book, ...)
. Beside writting an explicit JPQL, we can rely on Spring Data Query Builder mechanism or, even better, on Query By Example (QBE) API. In this context, QBE API is quite useful if the entity has a significant number of attributes and:
- for all attributes, we need a head-to-head comparison of each attribute value to the corresponding column value
- for a subset of attributes, we need a head-to-head comparison of each attribute value to the corresponding column value
- for a subset of attributes, we return true at first match between an attribute value and the corresponding column value
- any other scenario
Key points:
- the repository,
BookRepository
extendsQueryByExampleExecutor
- the application uses
<S extends T> boolean exists(Example<S> exmpl)
with the properprobe (an entity instance populated with the desired fields values) - moreover, theprobe relies on
ExampleMatcher
which defines the details on how to match particular fields
Note: Do not conclude that Query By Example (QBE) defines only theexists()
method. Check out all methodshere.
Description: This application is meant to highlight that the best place to use@Transactional
for user defined query-methods is in repository interface, and afterwards, depending on situation, on service-methods level.
Key points:
- this application is dissected in my book,Spring Boot Persistence Best Practices.
Description: This application is an example of using JPAJOINED
inheritance strategy andVisitor pattern.
Key points:
- this application allows us to define multiple visitors and apply the one that we want
Description: This application is an example of using JPAJOINED
inheritance strategy andStrategy pattern.
Key points:
- this application allows us to define multiple strategies and apply the one that we want
Description: This folder holds several applications that shows how each Spring transaction propagation works.
Key points:
- it is strongly recommended to follow these examples in the context of my book,Spring Boot Persistence Best Practices.
Description: This application is an example of using the JPAGenerationType.AUTO
for assigning automatically UUID identifiers.
Key points:
- store UUID in a
BINARY(16)
column
Description: This application is an example of manually assigning UUID identifiers.
Key points:
- store UUID in a
BINARY(16)
column
Description: This application is an example of using the Hibernate RFC 4122 compliant UUID generator,uuid2
.
Key points:
- store UUID in a
BINARY(16)
column
Description: This Spring Boot application is a sample that reveals how Hibernate session-level repeatable reads works. Persistence Context guarantees session-level repeatable reads. Check out how it works.
Key points:
- rely on two transactions implemented via
TransactionTemplate
Note: For a detailed explanation of this application consider my book,Spring Boot Persistence Best Practices
Description: This application is an example of using Hibernate-specifichibernate.enable_lazy_load_no_trans
. Check out the application log to see how transactions and database connections are used.
Key points:
- always avoid Hibernate-specific
hibernate.enable_lazy_load_no_trans
Description: This application is an example of cloning entities. The best way to achieve this goal relies on copy-constructors. This way we can control what we copy. Here we use a bidirectional-lazy@ManyToMany
association betweenAuthor
andBook
.
Key points:
- clone an
Author
(only thegenre
) and associate the corresponding books - clone an
Author
(only thegenre
) and clone the books as well
Description: This application is an example of using the Hibernate-specific,@DynamicUpdate
. By default, even if we modify only a subset of columns, the triggeredUPDATE
statements will include all columns. By simply annotating the corresponding entity at class-level with@DynamicUpdate
the generatedUPDATE
statement will include only the modified columns.
Key points:
- pro: avoid updating unmodified indexes (useful for heavy indexing)
- con: cannot reuse the same
UPDATE
for different subsets of columns via JDBC statements caching (each triggeredUPDATE
string will be cached and reused accordingly)
Description: This application is an example of logging execution time for a repository query-method.
Key points:
- write an AOP component (see
RepositoryProfiler
)
Description: This application is an example of using theTransactionSynchronizationAdapter
for overridingbeforeCommit()
,beforeCompletion()
,afterCommit()
andafterCompletion()
callbacks globally (application-level) and at method-level.
Key points:
- application-level: write an AOP component (see
TransactionProfiler
) - method-level: use
TransactionSynchronizationManager.registerSynchronization()
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely onSqlResultSetMapping
,NamedNativeQuery
and the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of native named query.
Key points:
- use
SqlResultSetMapping
,NamedNativeQuery
- for using Spring Data Projections check thisitem
Description: This is a sample application of usingSqlResultSetMapping
,NamedNativeQuery
andEntityResult
for fetching single entity and multiple entities asList<Object[]>
. In this application we rely on the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of native named query.
Key points:
- use
SqlResultSetMapping
,NamedNativeQuery
andEntityResult
Description: This application is an example of combining JPA named queries@NamedQuery
and Spring projections (DTO). For queries names we use the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of named query.
Key points:
- define the named queries
- define the proper Spring projection
Description: This application is an example of combining JPA named native queries@NamedNativeQuery
and Spring projections (DTO). For queries names we use the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of named native query.
Key points:
- define the named native queries
- define the proper Spring projection
Description: JPA named (native) queries are commonly written via@NamedQuery
and@NamedNativeQuery
annotations in entity classes. Spring Data allows us to write our named (native) queries in a typical*.properties
file inside theMETA-INF
folder of your classpath. This way, we avoid modifying our entities. This application shows you how to do it.
Warning: Cannot use native queries with dynamic sorting (Sort
). Nevertheless, usingSort
in named queries works fine. Moreover, usingSort
inPageable
works fine for both, named queries and named native queries. At least this is how it behave in Spring Boot 2.2.2. From this point of view, this approach is better than using@NamedQuery
/@NamedNativeQuery
ororm.xml
file.
Key points:
- define the named (native) queries in a file,
META-INF/jpa-named-queries.properties
- follow the Spring
{EntityName}.{RepositoryMethodName}
naming convention for a quick and slim implementation
Description: JPA named (native) queries are commonly written via@NamedQuery
and@NamedNativeQuery
annotations in entity classes. Spring Data allows us to write our named (native) queries in a typicalorm.xml
file inside theMETA-INF
folder of your classpath. This way, we avoid modifying our entities. This application shows you how to do it.
Warning: Pay attention that, via this approach, we cannot use named (native) queries with dynamic sorting (Sort
). UsingSort
inPageable
is ignored, therefore you need to explicitly addORDER BY
in the queries. At least this is how it behave in Spring Boot 2.2.2. A better approach relies on using aproperties file for listing the named (native) queries. In this case, dynamicSort
works for named queries, but not for named native queries. UsingSort
inPageable
works as expected in named (native) queries.
Key points:
- define the named (native) queries in a file,
META-INF/orm.xml
- follow the Spring
{EntityName}.{RepositoryMethodName}
naming convention for a quick and slim implementation
Description: JPA named (native) queries are commonly written via@NamedQuery
and@NamedNativeQuery
annotations in entity classes. This application shows you how to do it.
Warning: Pay attention that, via this approach, we cannot use named (native) queries with dynamic sorting (Sort
). UsingSort
inPageable
is ignored, therefore you need to explicitly addORDER BY
in the queries. At least this is how it behave in Spring Boot 2.2.2. A better approach relies on using aproperties file for listing the named (native) queries. In this case, dynamicSort
works for named queries, but not for named native queries. UsingSort
inPageable
works as expected in named (native) queries. And, you don't need to modify/pollute entitites with the above annotations.
Key points:
- use
@NamedQuery
and@NamedNativeQuery
annotations in entity classes - follow the Spring
{EntityName}.{RepositoryMethodName}
naming convention for a quick and slim implementation - avoid
Sort
andPageable
Description: This application is an example of combining JPA named queries listed in a properties file and Spring projections (DTO). For queries names we use the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of named query.
Key points:
- define the named queries in a properties file (e.g.,
jpa-named-queries.properties
) in a folder namedMETA-INF
the application classpath - define the proper Spring projection
Description: This application is an example of combining JPA named native queries listed in a properties file and Spring projections (DTO). For queries names we use the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of named native query.
Key points:
- define the named native queries in a properties file (e.g.,
jpa-named-queries.properties
) in a folder namedMETA-INF
the application classpath - define the proper Spring projection
Description: This application is an example of combining JPA named queries listed inorm.xml
file and Spring projections (DTO). For queries names we use the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of named query.
Key points:
- define the named queries in
orm.xml
file in a folder namedMETA-INF
the application classpath - define the proper Spring projection
Description: This application is an example of combining JPA named native queries listed inorm.xml
file and Spring projections (DTO). For queries names we use the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of named native query.
Key points:
- define the named native queries in
orm.xml
file in a folder namedMETA-INF
the application classpath - define the proper Spring projection
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely on named native queries and result set mapping viaorm.xml
and the{EntityName}.{RepositoryMethodName}
naming convention. This convention allows us to create in the repository interface methods with the same name as of native named query.
Key points:
- use
<named-native-query/>
and<sql-result-set-mapping/>
to map the native query toAuthorDto
class
Description: This application is a proof of concept for using Spring Projections(DTO) and cross joins written via JPQL and native SQL (for MySQL).
Key points:
- define two entities (e.g.,
Book
andFormat
- populate the database with some test data (e.g., check the file
resources/data-mysql.sql
) - write interfaces (Spring projections) that contains getters for the columns that should be fetched from the database (e.g., check
BookTitleAndFormatType.java
) - write cross joins queries using JPQL/SQL
Description: This application is an example of calling a MySQL stored procedure that returns a result set viaJdbcTemplate
andBeanPropertyRowMapper
.
Key points:
- rely on
JdbcTemplate
,SimpleJdbcCall
andBeanPropertyRowMapper
Description: This application is a sample of using the JPA@MappedSuperclass
and@EntityListeners
with JPA callbacks.
Key points:
- thebase class ,
Book
, is not an entity, it can beabstract
, and is annotated with@MappedSuperclass
and@EntityListeners(BookListener.class)
BookListener
defines JPA callbacks (e.g.,@PrePersist
)- subclasses of thebase class are mapped in tables that contains columns for the inherited attributes and for their own attibutes
- when any entity that is a subclass of
Book
is persisted, loaded, updated, etc the corresponding JPA callbacks are called
Advice: Always evaluateJOIN FETCH
and entities graphs before deciding to useFetchMode.JOIN
. TheFetchMode.JOIN
fetch mode always triggers anEAGER
load so the children are loaded when the parents are. Beside this drawback,FetchMode.JOIN
may return duplicate results. You’ll have to remove the duplicates yourself (e.g. storing the result in aSet
). But, if you decide to go withFetchMode.JOIN
at least pay attention to avoid N+1 issues discussed below.
Note: Let's assume three entities,Author
,Book
andPublisher
. BetweenAuthor
andBook
there is a bidirectional-lazy@OneToMany
association. BetweenAuthor
andPublisher
there is a unidirectional-lazy@ManyToOne
. BetweenBook
andPublisher
there is no association.
Now, we want to fetch a book by id (BookRepository#findById()
), including its author, and the author's publisher. In such cases, using Hibernate fetch mode,@Fetch(FetchMode.JOIN)
works as expected. UsingJOIN FETCH
or entity graph is also working as expected.
Next, we want to fetch all books (BookRepository#findAll()
), including their authors, and the authors publishers. In such cases, using Hibernate fetch mode,@Fetch(FetchMode.JOIN)
will cause N+1 issues. It will not trigger the expectedJOIN
. In this case, usingJOIN FETCH
or entity graph should be used.
Key points:
- using Hibernate fetch mode,
@Fetch(FetchMode.JOIN)
doesn't work for query-methods - Hibernate fetch mode,
@Fetch(FetchMode.JOIN)
works in cases that fetches the entity by id (primary key) like usingEntityManager#find()
, Spring Data,findById()
,findOne()
.
Description: This application is an example of assigning a database temporary ranking of values to rows via the window function,RANK()
. This window function is available in almost all databases, and starting with version 8.x is available in MySQL as well.
Key points:
- commonly, you don't need to fetch in the result set the temporary ranking of values produced by
RANK()
(you will use it internally, in the query, usually in theWHERE
clause and CTEs), but, this time, let's write a Spring projection (DTO) that contains a getter for the column generated byRANK()
as well - write several native querys relying on
RANK()
window function
Description: This application is an example of assigning a database temporary ranking of values to rows via the window function,DENSE_RANK()
. In comparison with theRANK()
window function,DENSE_RANK()
avoid gaps within partition. This window function is available in almost all databases, and starting with version 8.x is available in MySQL as well.
Key points:
- commonly, you don't need to fetch in the result set the temporary ranking of values produced by
DENSE_RANK()
(you will use it internally, in the query, usually in theWHERE
clause and CTEs), but, this time, let's write a Spring projection (DTO) that contains a getter for the column generated byDENSE_RANK()
as well - write several native querys relying on
DENSE_RANK()
window function
Description: This application is an example of distributing the number of rows in the specified (N) number of groups via the window function,NTILE(N)
. This window function is available in almost all databases, and starting with version 8.x is available in MySQL as well.
Key points:
- commonly, you don't need to fetch in the result set the temporary ranking of values produced by
NTILE()
(you will use it internally, in the query, usually in theWHERE
clause and CTEs), but, this time, let's write a Spring projection (DTO) that contains a getter for the column generated byNTILE()
as well - write several native querys relying on
NTILE()
window function
Description: Spring Data comes with the Query Builder mechanism for JPA that is capable to interpret a query method name (known as a derived query) and convert it into a SQL query in the proper dialect. This is possible as long as we respect the naming conventions of this mechanism. Beside the well-known query of typefind...
, Spring Data supports derived count queries and derived delete queries.
Key points:
- a derived count query starts with
count...
(e.g.,long countByGenre(String genre)
) - Spring Data will generate aSELECT COUNT(...) FROM ...
query - a derived delete query can return the number of deleted records or the list of the deleted records
- a derived delete query that returns the number of deleted records starts with
delete...
orremove...
and returnslong
(e.g.,long deleteByGenre(String genre)
) - Spring Data will trigger first aSELECT
to fetch entities in the Persistence Context, and, afterwards, it triggers aDELETE
for each entity that must be deleted - a derived delete query that returns the list of deleted records starts with
delete...
orremove...
and returnsList<entity>
(e.g.,List<Author> removeByGenre(String genre)
) - Spring Data will trigger first aSELECT
to fetch entities in the Persistence Context, and, afterwards, it triggers aDELETE
for each entity that must be deleted
Description: Property expressions can refer to a direct property of the managed entity. However, you can also define constraints by traversing nested properties. This application is a sample of traversing nested properties for fetching entities and DTOs.
Key points:
- Assume an
Author
has severalBook
and each book has severalReview
(betweenAuthor
andBook
there is a bidirectional-lazy@oneToMany
association, and, betweenBook
andReview
there is also a bidirectional-lazy@OneToMany
association) - Assume that we fetched a
Review
and we want to know theAuthor
of theBook
that has received thisReview
- via property expressions, we can write in
AuthorRepository
the following query that will be processed by the Spring Data Query Builder mechanism:Author findByBooksReviews(Review review);
- Behind the scene Spring Data will produce a
SELECT
with twoLEFT JOIN
- In this case, the method creates the property traversal
books.reviews
. The algorithm starts by interpreting the entire part (BooksReviews
) as the property and checks the domain class for a property with that name (uncapitalized). If the algorithm succeeds, it uses that property. If not, the algorithm splits up the source at the camel case parts from the right side into a head and a tail and tries to find the corresponding property — in our example,Books
andReviews
. If the algorithm finds a property with that head, it takes the tail and continues building the tree down from there, splitting the tail up in the way just described. If the first split does not match, the algorithm moves the split point to the left and continues. - Although this algorithm should work for most cases, it is possible for the algorithm to select the wrong property. Suppose the
Author
class has anbooksReview
property as well. The algorithm would match in the first split round already, choose the wrong property, and fail (as the type ofbooksReview
probably has no code property). To resolve this ambiguity you can use _ inside your method name to manually define traversal points. So our method name would be as follows:Author findByBooks_Reviews(Review review);
- More examples (including DTOs) are available in the application
Note: Fetchingread-only data should be done via DTO, not managed entities. But, there is no tragedy to fetch read-only entities in a context as follows:
- we need all attributes of the entity (so, a DTO just mirrors an entity)
- we manipulate a small number of entities (e.g., an author with several books)
- we use
@Transactional(readOnly = true)
Under these circumstances, let's tackle a common case that I saw quite a lot. There is even an SO answer about it (don't do this):
Description: Let's assume thatAuthor
andBook
are involved in a bidirectional-lazy@OneToMany
association. Imagine an user that loads a certainAuthor
(without the associatedBook
). The user may be interested or not in theBook
, therefore, we don't load them with theAuthor
. If the user is interested in theBook
then he will click a button of type,View books. Now, we have to return theList<Book>
associated to thisAuthor
.
So, at first request (query), we fetch anAuthor
. TheAuthor
is detached. At second request (query), we want to load theBook
associated to thisAuthor
. But, we don't want to load theAuthor
again (for example, we don't care aboutlost updates ofAuthor
), we just want to load the associatedBook
in a singleSELECT
. A common (not recommended) approach is to load theAuthor
again (e.g., viafindById(author.getId())
) and call theauthor.getBooks()
. But, this end up in twoSELECT
statements. OneSELECT
for loading theAuthor
, and anotherSELECT
after weforce the collection initialization. Weforce collection initialization because it will not be initialize if we simply return it. In order to trigger the collection initialization the developer callbooks.size()
or he rely onHibernate.initialize(books);
.
But, we can avoid such solution by relying on an explicit JPQL or Query Builder property expressions. This way, there will be a singleSELECT
and no need to callsize()
orHibernate.initialize();
Key points:
- use an explicit JPQL
- use Query Builder propery expressions
This item is detailed in my book,Spring Boot Persistence Best Practices.
Description: Behind the built-in Spring Datasave()
there is a call ofEntityManager#persist()
orEntityManager#merge()
. It is important to know this aspect in several cases. Among this cases, we have the entity update case (simple update or update batching).
ConsiderAuthor
andBook
involved in a bidirectional-lazy@OneToMany
association. And, we load anAuthor
, detach it, update it in thedetached state, and save it to the database viasave()
method. Callingsave()
will come with the following two issues resulting from callingmerge()
behind the scene:
- there will be two SQL statements, one
SELECT
(merge) and oneUPDATE
- the
SELECT
will contain aLEFT OUTER JOIN
to fetch the associatedBook
as well (we don't need the books!)
How about triggering only theUPDATE
instead of this? The solution relies on callingSession#update()
. CallingSession.update()
requires to unwrap theSession
viaentityManager.unwrap(Session.class)
.
Key points:
- calling
Session.update()
will trigger only theUPDATE
(there is noSELECT
) Session.update()
works withversioned optimistic locking mechanism as well (so,lost updates are prevented)
Description: This application is a sample of fetchingStreamable<entity>
andStreamable<dto>
. But, more important, this application contains three examples of how tonot useStreamable
. It is very tempting and comfortable to fetch aStreamable
result set and chop it viafilter()
,map()
,flatMap()
, and so on until we obtain only the needed data instead of writing a query (e.g., JPQL) that fetches exactly the needed result set from the database. Mainly, we just throw away some of the fetched data to keep only the needed data. But,is not advisable to follow such practices because fetching more data than needed can cause significant performance penalties.
Moreover, pay attention to combining two or moreStreamable
via theand()
method. The returned result may be different from what you are expecting to see. EachStreamable
produces a separate SQL statement and the final result set is a concatenation of the intermediate results sets (prone to duplicate values).
Key points:
- don't fetch more columns than needed just to drop a part of them (e.g., via
map()
) - don't fetch more rows than needed just to throw away a part of it (e.g., via
filter()
) - pay attention on combining
Streamable
viaand()
; eachStreamable
produces a separate SQL statement and the final result set is a concatenation of the intermediate results sets (prone to duplicate values)
Description: A common practice consists of exposing dedicated wrappers types for collections resulted after mapping a query result set. This way, on a single query execution, the API can return multiple results. After we call a query-method that return a collection, we can pass it to a wrapper class by manually instantiation of that wrapper-class. But, we can avoid the manually instantiation if the code respects the following key points.
Key points:
- the type implements
Streamable
- the type exposes a constructor (used in this example) or a
static
factory method namedof(…)
orvalueOf(…)
takingStreamable
as argument
Description: JPA 2.1 come with schema generation features. This feature can setup the database or export the generated commands to a file. The parameters that we should set are:
spring.jpa.properties.javax.persistence.schema-generation.database.action
: Instructs the persistence provider how to setup the database. Possible values include:none
,create
,drop-and-create
,drop
javax.persistence.schema-generation.scripts.action
: Instruct the persistence provider which scripts to create. Possible values include:none
,create
,drop-and-create
,drop
.javax.persistence.schema-generation.scripts.create-target
: Indicate the target location of the create script generated by the persistence provider. This can be as a file URL or ajava.IO.Writer
.javax.persistence.schema-generation.scripts.drop-target
: Indicate the target location of the drop script generated by the persistence provider. This can be as a file URL or ajava.IO.Writer
.
Moreover, we can instruct the persistence provider to load data from a file into the database via:spring.jpa.properties.javax.persistence.sql-load-script-source
. The value of this property represents the file location and it can be a file URL or ajava.IO.Writer
.
Key points:
- the settings are available in
application.properties
Description: Sometimes, we need to write in repositories certain query-methods that return aMap
instead of aList
or aSet
. For example, when we need aMap<Id, Entity>
or we useGROUP BY
and we need aMap<Group, Count>
. This application shows you how to do it viadefault
methods directly in repository.
Key points:
- rely on
default
methods andCollectors.toMap()
Description: Consider one of the JPA inheritance strategies (e.g.,JOINED
). Handling entities inheritance With Spring Data repositories can be done as follows:
Description: This application is a sample of logging only slow queries via Hibernate 5.4.5,hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS
property. A slow query is a query that has an execution time bigger than a specificed threshold in milliseconds.
Key points:
- in
application.properties
addhibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely on JDK14 Records feature and Spring Data Query Builder Mechanism.
From Openjdk JEP359:
Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data.
Key points:Define theAuthorDto
as:
public record AuthorDto(String name, int age) implements Serializable {}
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely on JDK 14 Records, Constructor Expression and JPQL.
From Openjdk JEP359:
Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data.
Key points:
Define theAuthorDto
as:
public record AuthorDto(String name, int age) implements Serializable {}
Description: Fetching moreread-only data than needed is prone to performance penalties. Using DTO allows us to extract only the needed data. Sometimes, we need to fetch a DTO made of a subset of properties (columns) from a parent-child association. For such cases, we can use SQLJOIN
that can pick up the desired columns from the involved tables. But,JOIN
returns anList<Object[]>
and most probably you will need to represent it as aList<ParentDto>
, where aParentDto
instance has aList<ChildDto>
. For such cases, we can rely on a custom HibernateResultTransformer
. This application is a sample of writing a customResultTransformer
.
As DTO, we rely on JDK 14 Records. From Openjdk JEP359:
Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data.
Key points:
- define the Java Records as
AuthorDto
andBookDto
- implement the
ResultTransformer
interface
Description: Fetching more data than needed is prone to performance penalities. Using DTO allows us to extract only the needed data. In this application we rely on JDK14 Records feature,JdbcTemplate
andResultSetExtractor
.
From Openjdk JEP359:
Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data.
Key points:
- define the Java Records as
AuthorDto
andBookDto
- use
JdbcTemplate
andResultSetExtractor
Description: This application is a sample of using dynamic Spring projections via DTO classes.
Key points:
- declare query-methods in a generic manner (e.g.,
<T> List<T> findByGenre(String genre, Class<T> type);
)
- Batch Inserts In Spring Boot Style Via
CompletableFuture
And ReturnList<S>
Description: This application is a sample of usingCompletableFuture
for batching inserts. ThisCompletableFuture
uses anExecutor
that has the number of threads equal with the number of your computer cores. Usage is in Spring style. It returnsList<S>
:
- Batch Inserts In Spring Boot Style Via
CompletableFuture
And ReturnList<S>
(1) - Batch Inserts In Spring Boot Style Via
CompletableFuture
And ReturnList<S>
(2)
Description: This application is an example of causing a database deadlock in MySQL. This application produces an exception of type:com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
. However, the database will retry until transaction (A) succeeds.
Key points:
- startTransaction (A) and trigger a
SELECT
withPESSIMISTIC_WRITE
to acquire an exclusive lock to tableauthor
- Transaction (A) update
author
genre with success and sleeps for 10s - after 5s, start a concurrentTransaction B that trigger a
SELECT
withPESSIMISTIC_WRITE
to acquire an exclusive lock to tablebook
- Transaction (B) update
book
title with success and sleeps for 10s - Transaction (A) wakes up and attempt to update the book but it cannot acquire the lock holded byTransaction (B)
- Transaction (B) wakes up and attempt to update the author but it cannot acquire the lock holded byTransaction (A)
- DEADLOCK
- database retry and succeeds afterTransaction (B) releases the lock
Description: This application is a proof of concept of how to define a composite key having an explicit part (name
) and a generated part (authorId
viaSEQUENCE
generator).
Key points:
- use
@IdClass
Description: Sometimes we need to intercept the generated SQL that originates from Spring Data,EntityManager
, Criteria API,JdbcTemplate
and so on. This can be done as in this sample application. After interception, you can log, modify or even return a brand new SQL that will be executed in the end.
Key points:
- define an implementation of Hibernate
StatementInspector
SPI - configure this SPI in
application.properties
viaspring.jpa.properties.hibernate.session_factory.statement_inspector
281.Force inline params in Criteria API
NOTE Use this with high precaution since you open the gate for SQL injections.
Description: Sometimes we need to force inline params in Criteria API. By default, numeric parameters are inlined, but string parameters are not.
Key points:
- configure in
application.properties
the settingspring.jpa.properties.hibernate.criteria.literal_handling_mode
asinline
Description:Arthur Gavlyukovskiy provide a suite of Spring Boot starters for quickly integrateP6Spy,Datasource Proxy, andFlexyPool. In this example, we add Datasource Proxy, but please considerthis for more details.
Key points:
- for Maven, in
pom.xml
, add thedatasource-proxy-spring-boot-starter
starter - in
application.properties
enableDEBUG
level for logging
Description: This application is an example of using Java records as embeddable. This is available starting with Hibernate 6.0, but it was refined to be more accessible and easy to use in Hibernate 6.2
Key points:
- add Hibernate 6.2 (this is not default in Spring Boot 3.0.2 used here)
- define a record (
Contact
) - add this record in an entity (
Author
) via@Embedded
- fetch data into a DTO represented by another record (
AuthorDto
)
About
Collection of 300+ best practices for Java persistence performance in Spring Boot applications
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.