Hibernate is an ORM that greatly helps us in implementing and modeling our database in our applications. But when used incorrectly, it can cause many headaches. In this post, we will look at the most common errors with Hibernate, ranging from performance issues and excessive queries to ways to improve or save lines of code.
Using Eager Fetching
The use of FetchType defined as Eager is often one of the most common errors with Hibernate. The definition of Eager will have a crucial impact on the performance of our applications, affecting the overall processing and functioning of our application.
When we define a @OneToMany, @ManyToOne, @ManyToMany, or @OneToOne relationship, we can set the FetchType attribute to Eager, which means Hibernate will perform a full load when loading an entity.
In cases where we have many associated children or lists, loading everything will result in a join operation.
@Entity public class User{ @ManyToMany(mappedBy="users", fetch=FetchType.EAGER) private Set<Account> accounts= new HashSet<Account>(); ... }
For example, in the above case, every time we retrieve a user, we will also retrieve all associated accounts. While this may be necessary in some cases, having EAGER set on Account with more lists can potentially load the entire database for that user, negatively impacting performance.
To avoid problems with EAGER, it is best to use FetchType.LAZY. This delays the initialization of the relationship until it is actually requested, avoiding unnecessary joins or queries.
By default, JPA defines LAZY for relationships, so it’s better not to change it and try to work with LAZY.
Changing the default EAGER for ManyToOne and OneToOne relationships
By default in relationships, the part that carries the object of the referenced class is loaded as EAGER. It is set as the default because it generally does not have a significant impact on performance since usually only one record is retrieved. However, the problem arises when we fetch many entities along with an object. In these cases, performance can be affected, so it is much better to change it to LAZY.
@Entity public class Account{ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "fk_user") private User user; ... }
Using LAZY without controlling the responses
A very common thing we see when we enable traces to show queries in our logs is that more requests are made than what we have configured (n + 1 queries). Why does this happen? This is because Hibernate is lazily loading your collections, but when you perform a get on a relationship, a query is executed at that moment. This is another common mistake with Hibernate that we can avoid by analyzing the calls made to our collections after retrieving them.
For example, this error is very common when we use MapStruct in our application. We want to convert our entity object to a domain object, and we haven’t ignored the collection, so MapStruct will perform a get on our collection, resulting in as many queries as the number of objects in our list.
Therefore, when we use FetchType.LAZY, we must carefully control our queries and mappings to avoid unnecessary queries.
@Entity public class User{ @ManyToMany(mappedBy="users", fetch=FetchType.LAZY) private Set<Account> accounts= new HashSet<Account>(); ... }
If we have the above code and a MapStruct class where we convert User to UserDTO as follows:
@Mapper(componentModel = "spring") public interface UserMapper{ UserDto toDto(User user); }
We would be generating an extra query for each element in the Account list.
Using flush() when saving an entity
When we use flush(), we save the entity at the moment the statement is created. Sometimes this may be necessary if we need to continue working with the entity in some way or require immediate persistence. However, when a flush() is performed after creating or updating an entity, it forces Hibernate to perform a dirty check, meaning it needs to manage the traceability of all affected entities. This process impacts performance.
Hibernate manages all insertions, updates, and deletions in a stack to process them at the end of the transaction, saving time and trying to limit the impact and number of operations.
Obviously, this doesn’t mean that we shouldn’t use flush(). If there are many operations, we should avoid it and delegate this responsibility to Hibernate.
Deleting, updating, or inserting item by item
Avoid performing individual insertion, deletion, or update of list items when using Hibernate in applications.
It makes sense that performing individual operations against a database is more costly than doing it all at once, right? For this purpose, we can use native queries or Hibernate’s batch functionality, which allows us to perform bulk insertions and limit the number of queries.
Using projections in Hibernate
When trying to optimize our application, it is crucial to analyze whether we are retrieving more information than needed in our query. Obviously, the more information we retrieve, the longer the request to the database will take.
For cases where we don’t need to retrieve all the information, projections are perfect.
For example:
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<String> query = builder.createQuery(String.class); Root<User> user= query.from(User.class); query.select(user.get("name")); List<String> resultList = entityManager.createQuery(query).getResultList();
Using FindAll() without size limit
Many times, we tend to rely on using FindAll() or performing queries without filtering the size. Retrieving excessive records for list display negatively impacts performance.
For such cases, we should avoid using JPQL since it doesn’t allow pagination. However, by using queries or Hibernate functions, we can pass a pagination object as a parameter.
For example, we can create a Pageable object with Spring Data as follows:
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 50, Sort.by(orders));
Using Hibernate for everything
Hibernate and JPA play an essential role in our development when we require a database connection. But is it necessary to use them always?
The most important thing is to have a good understanding of their usage and when to use them to avoid impacting the performance of our application. It’s not always necessary to use Hibernate; we can create native queries to generate more specific results and improve performance.
Conclusion
In this post about the most common errors with Hibernate, we have seen typical problems and mistakes that occur when developing applications with Hibernate and JPA. Improper use of Hibernate and JPA can severely impact application performance and data access.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!