Mateusz Sobczak
Mateusz Sobczak

Reputation: 1623

Spring data JPA getById vs GetReferenceById

Does anyone know why getById and getReferenceById work differently? I'm using JpaRepository and the method getReferenceById doesn't throw EntityNotFoundException but getById throws this exception when the object doesn't exist

Upvotes: 7

Views: 21736

Answers (3)

Patrick
Patrick

Reputation: 935

What you are saying seems a bit odd, as the implementation of the deprecated org.springframework.data.jpa.repository.JpaRepository#getById just delegates to it's replacement, org.springframework.data.jpa.repository.JpaRepository#getReferenceById.

As you can see in the implementation of that method (org.springframework.data.jpa.repository.support.SimpleJpaRepository#getReferenceById) it is directly using the EntityManager#getReference method.
When using Hibernate this normally only creates a Proxy, and just when you access one of the fields the Proxy fetches the real values from the DB - or throwing an EntityNotFoundException in case it does not exist.

Could it be that you changed something in your code, or you are being tricked by your debugger trying to display the toString of your method?

Upvotes: 0

matio
matio

Reputation: 427

Sometimes when using only getById as a way to fetch the entity makes some major issues. Usually we prefer to use getReferenceById method instead to save some extra calls, as if we were doing such using getById there is an extra call to database. Let’s consider we have a Category child entity that is associated with the parent Product entity via the Product reference in the Category entity. Means each product has multiple categories. The code block that adds a new category to our product using productId as reference is below:

public Category addNewCategory(String categoryName, Long productId) {           
    Category category = new Category()
        .setName(categoryName)
        .setProduct(productRepository.findById(productId)
            .orElseThrow(
                ()-> new EntityNotFoundException(
                    String.format(
                        "Product with id [%d] was not found!",
                        productId
                    )
                )
            )
        );

    categoryRepository.save(category);

    return category;
}

If you want to add a category to a product, you first need to call findProductById each time you insert a new Category. When you got the Product then you insert it as a reference to the Category entity. In this case Spring Data JPA would generate the following sql:

SELECT
product0_.id AS id1_0_0_,
product0_.slug AS name2_0_0_,
product0_.title AS title3_0_0_
FROM
 product product0_
WHERE
 product0_.id = 1

SELECT nextval ('hibernate_sequence')

INSERT INTO category (
 product_id,
 name,
 id
)
VALUES (
 1,
 'book',
 1
)

This query was generated by the findById method call, which is meant to load the entity in the current Persistence Context. However, in our case, we don’t need that. We just want to save a new Category entity and set the product_id Foreign Key column to a value that we already know.

But, since the only way to set the underlying product_id column value is to provide a Product entity reference, that’s why many developers end up calling the findById method.

In our case, running this SQL query is unnecessary because we don’t need to fetch the parent Product entity. But how can we get rid of this extra SQL query? We can use getReferenceById method instead.

public PostComment addNewCategory(String name, Long productId) {
    Category category = new Category()
       .setName(name)
       .setProduct(productRepository.getReferenceById(productId));

       categoryRepository.save(category);

       return category;
}

When calling the same addNewCategory method now, we see that the Product entity is no longer fetched as it will be fetched from a cached location where JPA holds entity Proxy. This way we can optimize our Spring applications that uses database intensively.

Upvotes: 3

Simon Martinelli
Simon Martinelli

Reputation: 36123

getReferenceById returns a proxy and doesn't do a database call therefore no exception is called as JPA doesn't know if the entity with this ID exists.

getReferenceById executes EntityManager.getReference and the docs says:

T getReference​(java.lang.Class entityClass, java.lang.Object primaryKey)

Get an instance, whose state may be lazily fetched. If the requested instance does not exist in the database, the EntityNotFoundException is thrown when the instance state is first accessed. (The persistence provider runtime is permitted to throw the EntityNotFoundException when getReference is called.) The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open.

Source: https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/entitymanager

Upvotes: 3

Related Questions