The article is a part of JPA & Spring pitfalls series. Below you can find the actual list of all articles of the series:
- JPA: N+1 SELECT problem
- JPA: 8 common pitfalls
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 eight most common programmers 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.
#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 processing of a big amount of data.
To learn more about the N+1 SELECT problem and 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 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 exactly 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 database until you explicitly ask for it by putting
@OrderColumnannotation along with your collection mapping (
#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 possibility of placing the annotations on class fields (field access) or on 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
@Idwill 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
@Accessannotation 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 of 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 JPA provider make it safe for you:
Query itemByNameQuery = entityManager.createNativeQuery("SELECT i FROM Item i WHERE i.name = :name");
#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 loading into memory, because it may turn out that when you are testing your code using local database everything works well, but after deploying the code to production you may get an OutOfMemoryError due to 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 solution, so stay tuned to our blog.
#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:
I recommend having them always enabled for the local setup, whereas disabling them for production to avoid excessive logging.
Essence in a nutshell
- 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
- 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 from SQL Injection.
- When retrieving a list of entities always remember to think how much data you may actually be loading 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!