Categories: Spring Boot

ManyToOne bidirectional association in Hibernate — common issues solved

This post covers issues that may occur when you map entities with ManyToOne bidirectional association. Read about mapping this relation while avoiding LazyInitializatinException, circular dependencies, n+1 select problem, wasting resources and false-positives in tests.

What we are going to build

In this example we are going to map the relation between meals and categories in a Spring Boot project using Hibernate. The project is build as a REST service. Therefore, we can easily call for all data we need to evaluate the correctness of our model.

You can examine the model on the picture below:

You can find the example project used in this post in the hibernate-basics-cheatsheet repository. Feel free to clone the repo and explore the hibernate features on your own.

Requirements

Map ManyToOne bidirectional association

Many meals can belong to one category. We want to:

  • ask for meals and receive the categories associated with them in the same response;
  • get a Category object and have all associated meals accessible from it.

For the Meal entity we are going to define the category property with the @ManyToOne annotation:

// hibernate-basics-cheatsheet/src/main/java/in/keepgrowing/hibernatebasicscheatsheet/meal/model/Meal.java
…
@Entity
public class Meal {
    …
    @ManyToOne
    private Category category;
…

Thanks to the default fetch type, in this case — EAGER, every Meal instance encloses the related category. In other words, when we get a meal, it will be delivered with information about its category by default.

For the Category entity we are going to define the meals property with the @OneToMany annotation:

// hibernate-basics-cheatsheet/src/main/java/in/keepgrowing/hibernatebasicscheatsheet/meal/model/Category.java
…
@Entity
public class Category {
…
    @OneToMany(mappedBy = "category")
    @JsonIgnore
    private List<Meal> meals;
…

The default fetch type for this association is LAZY. The related meals won’t be loaded with the requested category. We will have to explicitly ask for them. Because we run this example in a REST API, we need to use @JsonIgnore annotation to avoid the following error:

"Could not write JSON:
failed to lazily initialize a collection of role: in.keepgrowing.hibernatebasicscheatsheet.category.Category.meals,
could not initialize proxy - no Session(…)"

The Category entity is already detached from the session when Jackson tries to access associated meals that haven’t been loaded. We have to ignore meals in the expected JSON response. Otherwise, the API will throw a LazyInitializationException when trying to build the response.

Identify the owning side

Relational databases operate only on unidirectional relations — using foreign keys. To properly map our bidirectional relation between entities to unidirectional relation between tables, we need to specify which table owns the FK.

Since one category can have a vast amount of meals, we don’t want Hibernate to track changes in meals from the category side. You can check on the schema of the database, in the beginning of this post, that the meal table holds the category_id field as the FK.

To reflect that in our mapping, we use mappedBy="category" when defining the OneToMany side in the Category entity. That tells Hibernate to track the other side of the association — modification made on this side (Category) are already mapped by the other side (Meal::category).

Hibernate — What is happening under the hood when we ask for meals

For now, lets’ request all meals from our API in Postman:

The response contains all meals available. Every one of them comes with the instance of the Category that is associated with it (categories don’t contain meals in the response).

We can examine all queries performed by Hibernate in the console (see application properties in the Requirements section). Below you can see the query for all meals:

select
    meal0_.id as id1_2_,
    meal0_.category_id as category4_2_,
    meal0_.gluten_presence as gluten_p2_2_,
    meal0_.name as name3_2_ 
from
    meal meal0_

For every category associated with an existing meal there is another query. It’s the consequence of the default EAGER fetch type for the ManyToOne association between meals and categories.

The select for BREAKFAST category:

select
    category0_.id as id1_1_0_,
    category0_.name as name2_1_0_ 
from
    category category0_ 
where
    category0_.id=1

The select for SNACK category:

select
    category0_.id as id1_1_0_,
    category0_.name as name2_1_0_ 
from
    category category0_ 
where
    category0_.id=2

In the data.sql file we prepared also a third category — LUNCH, but its ignored in this case, as there is no meal associated with it.

If you don’t need categories when asking for meals, change the fetch type:

// hibernate-basics-cheatsheet/src/main/java/in/keepgrowing/hibernatebasicscheatsheet/meal/model/Meal.java
…
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    private Category category;
…

Later in this post (n+1 select issue in action), we are going to consider consequences of the default Hibernate behavior.

How to load lazy objects in Hibernate

Let’s ask for all categories in Postman:

We can see the following query in the console:

select
    category0_.id as id1_1_,
    category0_.name as name2_1_ 
from
    category category0_

The query asks only for the id and name of every category, there is no mention of the related meals. That’s why we had to use @JsonIgnore — to ignore meals in the JSON body.

n+1 select issue in action

The reason why LAZY fetch type is applied by default for the OneToMany relation is to optimize queries to the database. Loading nobody knows how many meals every time we ask for a category could significantly lower the performance of our API. Therefore, Hibernate by default provides us only with categories. Trying to access the related meals after categories are retrieved from the database will result in:

LazyInitializationException:
failed to lazily initialize a collection of role: in.keepgrowing.hibernatebasicscheatsheet.category.Category.meals,
could not initialize proxy - no Session

The solution is not to stop Hibernate from detaching retrieved objects from the persistence layer. It will allow us to reach for missing data but also introduce the n+1 select issue

To examine the n+1 select problem, we are going to add the @Transactional on a function that calls for categories and try to access meals:

// hibernate-basics-cheatsheet/src/main/java/in/keepgrowing/hibernatebasicscheatsheet/category/CategoryController.java
…
@RestController
@RequestMapping("api/categories")
public class CategoryController {

    private CategoryService service;

    public CategoryController(CategoryService service) {
        this.service = service;
    }

    @GetMapping
    @Transactional
    public List<Category> findAll() {
        List<Category> categories = service.findAll();
        categories.forEach(c->System.out.println(c.getMeals()));
        return categories;
    }
…

Thanks to that, the meals will be retrieved from the database when we ask for them, for every category, including the one that has no meals so far (the empty array in the middle):

[Meal{id=1, name='Oatmeal', glutenPresence=GLUTEN_FREE, category=Category{id=1, name='BREAKFAST'}}]
[]
[Meal{id=2, name='Almond cookie', glutenPresence=CONTAINS_GLUTEN, category=Category{id=2, name='SNACK'}}, Meal{id=3, name='Chocolate cookie', glutenPresence=CONTAINS_GLUTEN, category=Category{id=2, name='SNACK'}}]

That code will work. The problem gets obvious when we examine the queries printed in the console. First, the database is called for all categories like was shown in the beginning of this section. But when we iterate over the categories to print meals, the database receive one query for meals per every category, like the example one shown below:

select
    meals0_.category_id as category4_2_0_,
    meals0_.id as id1_2_0_,
    meals0_.id as id1_2_1_,
    meals0_.category_id as category4_2_1_,
    meals0_.gluten_presence as gluten_p2_2_1_,
    meals0_.name as name3_2_1_ 
from
    meal meals0_ 
where
    meals0_.category_id=1

Then the requests for the remaining categories are sent respectively. If you have to operate on many categories, you may notice performance issues.

I’m going to remove the @Transactional annotation from the Category controller method, because I put it there only to expose the n+1 problem.

We observed the same issue when asking for meals. Default EAGER fetch type in the relation between Meal and Category results in performing one query for all meals and multiple queries for all associated categories.

Don’t waste resources and deal with n+1 select problem

We made the Meal-Category relation bidirectional because we needed to be able to call both entities for related objects. To ensure that we are not wasting resources, we have to be certain about what we really need to receive from the API – complete associated objects, just their ids or maybe we won’t be really reaching to the category for its meals and the association could be unidirectional?

Always measure the performance of the application before and after the optimization. Apply change only where it is needed.

You need to access complete Meal instances

We need the categories to already contain associated meals when we retrieve them from the database. In order to achieve this, we are going to use the following custom query:

// hibernate-basics-cheatsheet/src/main/java/in/keepgrowing/hibernatebasicscheatsheet/category/CategoryRepository.java
…
public interface CategoryRepository extends CrudRepository<Category, Long> {

    @Query("SELECT DISTINCT c" +
            " FROM Category c" +
            " LEFT JOIN FETCH c.meals")
    List<Category> findAll();
…

Which gives us the following response when the http://localhost:8080/api/categories endpoint is hit in Postman:

In the console we will see the following query:

    select
        distinct category0_.id as id1_1_0_,
        meals1_.id as id1_2_1_,
        category0_.name as name2_1_0_,
        meals1_.category_id as category4_2_1_,
        meals1_.gluten_presence as gluten_p2_2_1_,
        meals1_.name as name3_2_1_,
        meals1_.category_id as category4_2_0__,
        meals1_.id as id1_2_0__ 
    from
        category category0_ 
    left outer join
        meal meals1_ 
            on category0_.id=meals1_.category_id

If you need allergens associated with meals when using Category instance, add one more JOIN to the query:

// hibernate-basics-cheatsheet/src/main/java/in/keepgrowing/hibernatebasicscheatsheet/category/CategoryRepository.java
…
    @Query("SELECT DISTINCT c" +
            " FROM Category c" +
            " LEFT JOIN FETCH c.meals m" +
            " LEFT JOIN FETCH m.allergen")
    List<Category> findAll()
…

You can use the following custom query in MealRepository to tell Hibernate to retrieve all meals and related categories in one query:

// hibernate-basics-cheatsheet/src/main/java/in/keepgrowing/hibernatebasicscheatsheet/meal/model/MealRepository.java
…
    @Query("SELECT m" +
            " FROM Meal m" +
            " LEFT JOIN FETCH m.category c")
    List<Meal> findAll();
…

The above code will result in the following query:

select
    meal0_.id as id1_2_0_,
    category1_.id as id1_1_1_,
    meal0_.category_id as category4_2_0_,
    meal0_.gluten_presence as gluten_p2_2_0_,
    meal0_.name as name3_2_0_,
    category1_.name as name2_1_1_ 
from
    meal meal0_ 
left outer join
    category category1_ 
        on meal0_.category_id=category1_.id

All meals and related categories are now loaded with only one query.

The database executes only one select, but we are loading unspecified amount of full objects when requesting categories. It can be troublesome if the objects are considerably large and/or have many relations/related collections. If loading full objects decreases the API performance, consider loading only ids of the associated objects.

You only need the ids of associated meals when asking for categories

Check out the How to get json response only with an id of the related entity post to see how you can fetch an entity with an id of a related object while preserving proper serialization and deserialization. The post covers using @JsonIdentityInfo annotation alongside with @JsonIdentityReference and deals with common pitfalls when converting an entity with only ids of associated objects into a JSON response.

Avoid circular calls

Now, when the response for the query for categories includes the associated meals, it may be tempting to remove the @JsonIgnore annotation from the meals property in the Category entity. However, the API won’t be able to properly serialize responses to JSON, due to a cyclic dependency.

The response with all categories will contain meals and every meal in it contains a category that contains meals… and so on. When the response is being converted to the JSON we get the following error:

Request processing failed; 
nested exception is org.springframework.http.converter.HttpMessageNotWritableException: 
Could not write JSON: 
Infinite recursion (StackOverflowError); 
nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
Infinite recursion (StackOverflowError) (through reference chain: 
Meal["category"]->Category["meals"]…

Avoid false-positives when testing associations

The following tests show that categories loaded with findAll() method enhanced with our custom query contain associated meals and a category loaded with the default findById() method doesn’t include its meals:

// hibernate-basics-cheatsheet/src/test/java/in/keepgrowing/hibernatebasicscheatsheet/category/CategoryRepositoryTest.java
…
@RunWith(SpringRunner.class)
@DataJpaTest
public class CategoryRepositoryTest {

    @Autowired
    private CategoryRepository repository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    public void findsAll() {
        List<Category> categories = repository.findAll();
        categories.forEach((c)-> entityManager.detach(c));
        Meal firstMeal = categories.get(0).getMeals().get(0);
        assertEquals("Oatmeal", firstMeal.getName());
    }

    @Test(expected = LazyInitializationException.class)
    public void findsById() {
        Optional<Category> category = repository.findById(1L);
        entityManager.detach(category.get());
        List<Meal> meals = category.get().getMeals();
        meals.forEach(m-> System.out.println(m.getName()));
    }
}

We had to manually detach categories from the persistent layer(line 16 and line 24). Why? Every test runs within a transaction. We can ask for categories and ask for meals later, and we won’t receive the LazyInitialization exception, because Category instances never leave the transaction. They stay attached to the entity manager for the whole test.

Make sure to detach entities from the persistence layer manually in tests (and to disable OSIV) if you want the LazyInitializationException to be thrown.

Conclusion

Don’t hesitate to print SQL Hibernate queries in the console when working on a data model for your API. Remember to enable it only on the development environment.

It’s crucial to see what is actually happening when we reach for entities. Spring Data and Hibernate do a great job in hiding implementation details from developers to facilitate their work. However, always dedicate enough time and attention to make sure that the data model is mapped in a proper and efficient way.

Photo by Antonio Barroro on StockSnap

little_pinecone

View Comments

  • Great post,

    However I have one issue:
    When I query all child records, only the first child record that was mapped to a particular parent will have the parent entity object in it. the rest will have an id referencing the parent enity.
    Here is an example: Getting all children from POSTMAN returns:

    [
        {
            "id": 1,
            "name": "child1",
            "parent": {
                "id": 1,
                "firstName": "..."
                ...
                }
        },
        {
            "id": 2,
            "name": "child2",
            "parent": 1
        }
        {
            "id": 3,
            "name": "child3",
            "parent": {
                "id": 2,
                "firstName": "..."
                ...
                }
        },
        {
            "id": 4,
            "name": "child4",
            "parent": 2
        }
    ]

    As you can see child2 only has "parent": 1 since child1 mapped to that parent first! Similarly child4 only has "parent": 2 since child3 mapped to that parent first!
    Can anyone explain this behaviour please? I tried fetch = FetchType.EAGER on parent but it did not help! I expect all children to have a comprehensive parent object to prevent another DB trip.
    Thanks in advance!

    I posed the question on stackoverflow... yet to get a response!
    https://stackoverflow.com/questions/61962472/jpa-jparepository-child-record-has-parent-id-instead-of-entity-record-if-paren
     

    • Thank you for letting me know. I'll include this issue in the post. I'm happy to see that you found the answer on Stack Overflow. Thank you once again for the comment and for including the link to your question on Stack Overflow.

  • Hi ,  in my case I have parent entity and three child entity with onetomany relationship but I want response from only first child not with second and third how to ignore that  being queried...

Recent Posts

Simplify the management of user roles in Spring Boot

Spring Security allows us to use role-based control to restrict access to API resources. However,…

3 years ago

Create a custom annotation to configure Spring Boot tests

A custom annotation in Spring Boot tests is an easy and flexible way to provide…

3 years ago

Keycloak with Spring Boot #4 – Simple guide for roles and authorities

Delegating user management to Keycloak allows us to better focus on meeting the business needs…

3 years ago

Keycloak with Spring Boot #3 – How to authorize requests in Swagger UI

Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…

3 years ago

Keycloak with Spring Boot #2 – Spring Security instead of Keycloak in tests

Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can…

3 years ago

Keycloak with Spring Boot #1 – Configure Spring Security with Keycloak

Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…

3 years ago