codete JPA 8 Common Pitfalls 1 main 5e35e03950
Codete Blog

JPA: 8 Common Pitfalls [Spring & JPA Pitfalls Series]

Michal Marciniec 7ff5ed9975

20/05/2019 |

6 min read

Michał Marciniec

The article is a part of the JPA & Spring pitfalls series, which you can check out here.

 

Preface

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!

Read: JPA & Spring Pitfalls

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!

Rated: 5.0 / 3 opinions
Michal Marciniec 7ff5ed9975

Michał Marciniec

Tech Lead at Codete. Michał is an eager fan of the fresh approach to Java programming. Enthusiast of Spring tech stack and refactoring techniques. Enjoys solving Java quirks, algorithmic puzzles, and... Rubik's cube. Privately, amateur drummer.

Our mission is to accelerate your growth through technology

Contact us

Codete Global
Spółka z ograniczoną odpowiedzialnością

Na Zjeździe 11
30-527 Kraków

NIP (VAT-ID): PL6762460401
REGON: 122745429
KRS: 0000983688

Get in Touch
  • icon facebook
  • icon linkedin
  • icon instagram
  • icon youtube
Offices
  • Kraków

    Na Zjeździe 11
    30-527 Kraków
    Poland

  • Lublin

    Wojciechowska 7E
    20-704 Lublin
    Poland

  • Berlin

    Bouchéstraße 12
    12435 Berlin
    Germany