The article is a part of the JPA & Spring pitfalls series, which you can check out here.
JPA makes DB operations (and thus our lives too) much easier than when using raw JDBC. However, with its little pinch of magic, it brings also a few dangers that we must be aware of if we want to use it right.
In this article, I’ll shortly outline the eight most common programmer mistakes and JPA pitfalls. If you find any topic interesting, I firmly recommend reading more about it, so you would be aware of all the common pitfalls and solutions for them. Let’s go!
JPA: 8 Common Pitfalls
#1 - N+1 SELECT Problem
N+1 SELECT doesn’t only happen in JPA, but it is a common ORM problem related to lazy loading optimization. Although it’s quite easy to understand and solve, it may be hard to find in your code (since it doesn’t cause any exceptions directly) and it may cause a significant performance drop when affects the processing of a big amount of data. To learn more about the N+1 SELECT problem and the best solutions for it in JPA, read this article.
#2 - Solving LazyInitializationException with eager fetching
I put that in a separate section, however, the ways of solving this problem are usually the same as for N+1 SELECT, the source of the problem is completely different.
LazyInitializationException indicates access to unfetched data outside of a session context. For example, when an uninitialized proxy or collection is accessed after the session was closed. Therefore, depending on the situation, one solution may be prolonging the session context, so the proxy may reach the database from there, and the other may be loading the lazy associations to prevent uninitialized proxies to leave the session context.
In the second case the easiest and unfortunately very common “solution” is just changing the relation’s fetch type from lazy to eager, which in most cases is just replacing one problem with another. Ways to solve that problem are the same as those for the N+1 SELECT problem described in this article.
#3 - Assuming that using List preserves order
By definition, all List implementations in Java preserve insertion order. However, JPA doesn’t guarantee that this order will also be preserved when inserting/retrieving data to/from the database until you explicitly ask about it by putting @OrderColumn annotation along with your collection mapping (@OneToMany, @ManyToMany, or @ElementCollection).
#4 - Overriding equals() & hashCode() unthoughtfully
Hibernate uses hashCode() and equals() methods when reattaching detached instances or when you use Set collection for storing entities. Taking into account that entity classes are usually mutable and that the meaning of equality from Java perspective is slightly different from Hibernate’s, the task to implement equals() & hashCode() and not break any contract at the same time becomes quite difficult and requires deeper knowledge.
This is a fairly wide topic and usually choosing the right approach depends on a specific case. I recommend reading Vlad Mihalcea’s posts on this topic to learn about the threats and potential solutions.
#5 - Mixing field and method annotations
JPA gives you the possibility of placing the annotations on class fields (field access) or getter methods (property access), but by default, you can’t mix them within one entity class. If you do, everything will depend on where you placed @Id annotation, because only annotations with the same access type as @Id will be taken into effect, whereas the rest will just be ignored causing your application to run with invalid JPA mapping or, if you’re lucky, to fail to start up.
Since JPA 2.0, we can overcome this partially but need to explicitly use @Access annotation and specify the default access type, and pointing which attributes should be accessed differently.
#6 - Not worrying about SQL Injection
Referring to OWASP Top 10 Application Security Risks report, injection attacks are the most popular way of performing attacks against applications. One of them is SQL Injection. It’s easy to make the application immune to SQL Injection attacks since most DB access mechanisms provide tools for it, but there are still many programmers who don’t know about it. Remember to never again concatenate your query with user input like this:
entityManager.createNativeQuery("SELECT i FROM Item i WHERE i.name = " + userInputName);
Instead, use query parameters and let the JPA provider make it safe for you:
Query itemByNameQuery = entityManager.createNativeQuery("SELECT i FROM Item i WHERE i.name = :name"); itemByNameQuery.setParameter("name", userInputName);
#7 - Not using pagination at all or using not carefully enough
When retrieving a list of all entities from a DB table always remember to think how much data you may actually be loaded into memory. It may turn out that when you are testing your code using a local database everything works well, but after deploying the code to production you may get an OutOfMemoryError due to a large amount of data to be loaded from the production database.
Trust me, I’ve been there and it’s nothing pleasant…
What is fun, you still may get the same OOM even if you started using pagination, but didn’t remember about Hibernate’s caching mechanism. I’m about to write a short post about it with code snippets and solutions. Update: You can read more about this topic in this article.
#8 - Ignoring debug statistics
Analyzing debug statistics is a basic thing when working with JPA. This way you can make sure you may ensure that underneath everything works as you would expect or you may detect issues in the early stages. It’s important to do it even when the changes around your DB model look innocent, sometimes even fairly simple cases may cause noticeable inefficiencies.
It’s very simple to turn them on, for example for Spring Boot + Hibernate setup it is as simple as adding just a bunch of properties:
spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.generate_statistics=true logging.level.org.hibernate.SQL=DEBUG
I recommend having them always enabled for the local setup, whereas disabling them for production to avoid excessive logging.
You can read this article to see how analyzing debug statistics may help to spot inefficiencies, sometimes even in fairly simple cases.
JPA: 8 Common Pitfalls - wrap up
- Think at least twice if you intend to solve a problem by changing lazy loading to eager loading. Usually, this is not the right solution.
- Lists preserve insertion order in Java, but in JPA not necessarily (until you add @OrderColumn annotation).
- Be careful with equals() and hashCode() in entity classes.
- Place your annotations either only on fields or only on getters, don’t mix.
- Never concatenate SQL/JPQL queries with user input. Use query parameters to prevent SQL Injection.
- When retrieving a list of entities always remember to think about how much data you may actually be loaded into memory and whether you shouldn’t use pagination.
- Analyze JPA logs and statistics to make sure everything works as expected.
Thanks for reading, I hope that you enjoyed it. Take care, keep calm and use JPA!