Reputation: 1623
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
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
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
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