GNUN with Spring Data
What?
Recently, I have read a term called GNUN. It's said:
Give Nothing Until Needed
Oh wow, from a developer's perspective, frameworks offer so much—they make everything look easier.
We can clearly see their benefits when comparing how we work with databases. In the past, we used raw JDBC libraries, but now we have ORM tools. It's cool, right? ORM tools offer many functionalities, and working with databases has never been easier!
Okay, cool. Let's dive deeper into Spring Data. What are the problems?
Most of the time, the problems come from how people use the framework rather than the framework itself.
Problem
Working with ORMs, I'm sure most of us have encountered the N+1 problem at least once.
The N+1 problem arises in Hibernate due to the way lazy loading is implemented for associations between entities. When lazy loading is used, related entities are fetched from the database only when they are accessed for the first time.
In simple terms, the N+1 problem happens when the system executes more queries than necessary.
Example:
Imagine I have 4 cars, and each car has 4 wheels. If you're familiar with SQL, you’d expect to retrieve all the data in a single query like this:
Expected
SELECT * FROM car c INNER JOIN wheel w ON c.ID = w.CAR_ID;
Actual
- Get all cars:
SELECT * FROM car;
- Then, for each car (4 cars in total), fetch its wheels:
SELECT * FROM wheel WHERE CAR_ID = ?;
This means one query for the cars plus 4 additional queries for the wheels, resulting in N+1 queries (1 query for the cars + N queries for the wheels).
Oh, it’s such a common issue! Yet, we still see it everywhere. It feels like everyone knows about it, but somehow, it keeps happening.
How?
When you design your entity, consider the following things:
GN: Give Nothing
Let's use LAZY Fetch for all referenced entities.
Example:
Going back to the car example, EAGER Fetch means we give them more data than they need.
List<Car> cars = carRepository.findById(id);
Using above code will bring Wheel
along with Car
. By setting the fetch type to LAZY Fetch, the code will only return the car entity.
UN: Untill Needed
After initiating, what if we need to fetch referenced entities? Now we are using LAZY Fetch, so if we try to access them through the getter, we might potentially face:
- N+1 problem: If we access the referenced entities in a loop, it will trigger one query for the parent entity and N additional queries for each referenced entity.
- LazyInitializationException: If the session is closed or the transaction is finished, and we try to access the lazy-loaded entity, Hibernate will throw this exception.
What's the best solution? Spring Data give us some option.
EntityGraph
An EntityGraph is a powerful way to manage fetching strategies in Spring Data JPA. It allows you to specify which associations should be fetched eagerly in a given query, without changing the global fetching strategy on the entity level.
How does it work?
Instead of relying on the default fetch strategy (EAGER or LAZY), you can define an EntityGraph to specify exactly which associations should be fetched eagerly for a particular query.
@EntityGraph(attributePaths = {"wheels"})
List<Car> findById(Long id);
JOIN FETCH
Another solution provided by Spring Data to handle fetching strategies efficiently is using JOIN FETCH in JPQL or HQL queries. This is useful when you want to fetch related entities eagerly in a single query, without changing the default fetching strategy on the entity level.
How does it work?
In a JOIN FETCH query, you can explicitly join the associated entities and fetch them eagerly in a single query. This eliminates the need for additional queries when accessing the related entities.
Example: Using JOIN FETCH in a query
@Query("SELECT c FROM Car c JOIN FETCH c.wheels WHERE c.id = :id")
Car findByIdWithWheels(@Param("id") Long id);
Recap
I recommend using these concepts as a checklist when working with ORM. By being aware of them, you can ensure that your code is not only efficient but also more maintainable and aligned with the principles of Give Nothing Until Needed. This approach helps you keep database interactions minimal and fetch only what’s truly necessary.
I hope this post helps you navigate the complexities of ORM frameworks and empowers you to write better, more efficient code.
Subscribe to my newsletter
Read articles from Duy Le Van directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by