10

Spring Boot Data Access Layer Best Practices

 2 years ago
source link: https://towardsdev.com/spring-boot-data-access-layer-best-practices-c544d400de7b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring Boot Data Access Layer Best Practices

In this article, we review best practices that are very effective to optimize spring boot data access layer.

Spring boot JPA has added some interface on JPA. JPA is only a specification not an implementation. There are various ORMs that implement JPA like Hibernate and EclipseLink.

Hibernate provides many benefits like object-mapping, caching, multiple transactions, database independent queries, etc. But It has to manage the lifecycle of each entity (transient, managed, detached, removed) that causes a little overhead. But lack of sufficient knowledge on how to use it can cause serious performance problems and may cause product failure..

For optimizing application data access layers based on Spring boot JPA and Hibernate, I recommend these tips and references.

Monitoring and Testing

  • For 100% database compatibility between production and test, use test containers instead of in-memory databases (H2, Fongo) for tests.
  • Use unit tests to assert and count SQL statements to ensure your code does not generate additional queries. Use this link for more information.
  • Log slow query with threshold by adding this line to the configuration file(It available in Hibernate 5.4.5+), otherwise use third-party libraries such as datasource-proxy.
hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=30

Transactions

In transactions, we should work with read-only and small transactions as much as possible. Following instructions help us to improve transactions performance:

  • Use these configurations to postpone connection acquisition until it’s really needed:
spring.datasource.hikari.auto-commit=false
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
  • Use @Transactional(readOnly=true) to load entities in read-only state.
  • @Transactional() load entity in read-write state. Read-only state is much cheaper than the read-write state.
  • Don’t use @Transactional() on private or protected methods. Because it doesn’t work in this mode.
  • Use @Transactional(readOnly=true) in Repository Interfaces and @Transactional() in service methods that have updating or creation operations (or any methods that need to run as a single transaction).
  • Don’t use @Transactional() in service or controller classes.
  • Refactor long during transaction methods to decrease transaction running time. Break them into smaller transactions.

Id Generation Type

  • Don’t use GenerationType.AUTO, because in Mysql it maps to GenerationType.TABLE that causes three queries when you want to insert a new row.
  • GenerationType.SEQUENCE is a good choice. But, if the underlying DBMS is not able to support it, use GenerationType.IDENTITY (in Mysql).
  • Don’t use composite keys and long keys as a primary key. Avoid using UUID/GUID as a primary key, because it is random and it causes re-organisation of the B-tree and finally it decreases performance.

Associations and Relations

We should prevent creating unexpected tables and executing more SQL statements. Consider these:

  • Use bidirectional @OneToMany associations(1⇆m) instead of unidirectional ones(1→m).
  • Using @JoinColumn can provide benefits in unidirectional @OneToMany, but still bidirectional @OneToMany better than it.
  • Unidirectional @ManyToOne is totally efficient.
  • Always use cascading from parent-side to child-side.
  • In @OneToMany, when loading entities in multiple sessions or working with detached entities, override equals() and hashCode() methods.
  • In any relationship type, use lazy fetching for each part of the associations.( E.g. @ManyToOne(fetch = FetchType.LAZY))
  • In @ManyToMany choose the owner of the relationship and use Set instead of List.

Fetching and Queries

  • When you don’t want to change the requested entity, use DTO to map fetched read-only entities.
  • When to want modify an entity and need to load the child side of relation use JOIN Fetch in JPQL query instead of eager loading. If we want to fetch all entities on the left side then we use LEFT JOIN FETCH.
  • In JPQL/SQL, JOIN maps to INNER JOIN and LEFT/RIGHT/FULL JOIN equals to LEFT/RIGHT/FULL OUTER JOIN.
  • Explicit JOIN is better than implicit JOIN. In some cases implicit JOIN maps to CROSS JOIN not INNER JOIN.
  • In parent-child entity queries when using JOIN FETCH, the result might contain object reference duplicates. It duplicates the parent record for every child row that’s going to be fetched. The DISTINCT keyword should only be applied after the ResultSet is obtained from JDBC, therefore using INT_PASS_DISTINCT_THROUGH instead of DISTINCT is recommended. Don’t use this method for scalar queries.
  • Using Spring built-in methods, findById(), find(), or get() to fetch an entity by id is better than fetching it by specific JPQL/SQL query. These methods use the Hibernate caching mechanism ( First Level cache->Second Level cache->database), but JPQL/SQL queries fetch directly from the database.
  • For fetching an entity we can use Spring built-in methods, findById() (it uses EntityManager#find() and return axact the declared model) or getOne()(it uses EntityManager#getReference() and return Hibernate-specific proxy object)
  • In @ManyToOne or @OneToOne associations, when we need to persist a child entity with reference to its parent entity, It’s better to use getOne() to get the parent side.
  • In flush time,Hibernate finds modified properties in the entity and updates them based on the dirty checking mechanism. Don’t use save() method in the updating task, because it is a duplicated task and causes additional Hibernate specific-internal operations(MergeEvent). Of Course, using save() method doesn’t generate additional queries.
  • To avoid N+1 problems (executing 1 query to retrieve the parent and N queries to retrieve the child entities), use JOIN FETCH or entity graph.
  • Always use pagination to make the result set as small as possible.
  • Open Session In View(OSIV) tries to avoid LazyInitializationException by binding a JPA EntityManager to request thread lifecycle. It is a bad approach and anti-pattern, the business layer should determine the fetching approach. It also causes a performance penalty. Use this configuration to turn of OSIV:
spring.jpa.open-in-view=false
  • A cache execution plan decreases load by caching prepared execution plans. The plan can be reused without the optimization overhead. If the RDBMS doesn’t cache execution plans(Mysql) then caching via IN clause parameter padding only increases the complexity of the statement and might slow down the query.
  • If you have a lot of queries, alter Hibernate Query Plan Cache (QPC) to cover all queries in your application. The default size for JPQL and Criteria API is 2048 and for native queries is 128.
spring.jpa.properties.hibernate.query.plan_cache_max_size = 3000
spring.jpa.properties.hibernate.query.plan_parameter_metadata_max_size=200
  • In oneToMany association, if you detach an entity, update it and save it, then this save() method causes an additional select join query. To avoid it and optimize merge operation by update, use Hibernate session update() method instead of JPA save() method directly.
val session=entityManager.unwrap(Session::class.java)
session.update(yourEntity)

Persistence Context

  • To clear persistence context after query execution, use @Modifying(clearAutomatically = true). It may delete unsaved changes.To prevent from deleting non-flushed entities in persistence context use @Modifying(flushAutomatically = true).
  • Persistence context is a first-level cache that sits between application and database. Hibernate uses it to handle the lifecycle of entities. For checking the status of entity in persistence context, this small code is helpful (Kotlin code) :
@PersistenceContext
private val entityManager: EntityManager? = null
Val persistenceContext= entityManager.unwrap(SharedSessionContractImplementor::class.java).persistenceContext

Element Collections

  • Use @ElementCollection with embedded or basic type not an entity. In this case,any crud operations one the embedded object are limited directly and you need the parent side to do these actions.
  • Don’t use @ElementCollection when you have a lot of inserts/deletes on the collection. It causes Hibernate to execute a lot of insertions/deletion operations that are not needed.

Pagination and Hibernate Types

  • Use powerful filtering (keyset pagination) instead of using offset pagination. offset method decreases performance in large databases because of fetching and dropping many rows.
  • When you need additional types(e.g., json) that are not supported by Hibernate ORM core, use the Hibernate Types repository.

References

There are three guys that created a lot of valuable documentation about Hibernate and Spring JPA:

  1. Vlad Mihalcea : He is a true champion in his career. He has provided various tutorial content and tools.
  2. Thorben Janssen: He is an active person with a lot of tutorial content.
  3. Anghel Leonard: He has written several interesting books.

I recommend these references very highly:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK