How does the FetchMode work in Spring Data JPA
Solution 1
I think that Spring Data ignores the FetchMode. I always use the @NamedEntityGraph
and @EntityGraph
annotations when working with Spring Data
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {
// default fetch mode is lazy.
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();
…
}
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name);
}
Check the documentation here
Solution 2
First of all, @Fetch(FetchMode.JOIN)
and @ManyToOne(fetch = FetchType.LAZY)
are antagonistic because @Fetch(FetchMode.JOIN)
is equivalent to the JPA FetchType.EAGER
.
Eager fetching is rarely a good choice, and for predictable behavior, you are better off using the query-time JOIN FETCH
directive:
public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
@Query(value = "SELECT p FROM Place p LEFT JOIN FETCH p.author LEFT JOIN FETCH p.city c LEFT JOIN FETCH c.state where p.id = :id")
Place findById(@Param("id") int id);
}
public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {
@Query(value = "SELECT c FROM City c LEFT JOIN FETCH c.state where c.id = :id")
City findById(@Param("id") int id);
}
Solution 3
Spring-jpa creates the query using the entity manager, and Hibernate will ignore the fetch mode if the query was built by the entity manager.
The following is the work around that I used:
Implement a custom repository which inherits from SimpleJpaRepository
-
Override the method
getQuery(Specification<T> spec, Sort sort)
:@Override protected TypedQuery<T> getQuery(Specification<T> spec, Sort sort) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<T> query = builder.createQuery(getDomainClass()); Root<T> root = applySpecificationToCriteria(spec, query); query.select(root); applyFetchMode(root); if (sort != null) { query.orderBy(toOrders(sort, root, builder)); } return applyRepositoryMethodMetadata(entityManager.createQuery(query)); }
In the middle of the method, add
applyFetchMode(root);
to apply the fetch mode, to make Hibernate create the query with the correct join.(Unfortunately we need to copy the whole method and related private methods from the base class because there was no other extension point.)
-
Implement
applyFetchMode
:private void applyFetchMode(Root<T> root) { for (Field field : getDomainClass().getDeclaredFields()) { Fetch fetch = field.getAnnotation(Fetch.class); if (fetch != null && fetch.value() == FetchMode.JOIN) { root.fetch(field.getName(), JoinType.LEFT); } } }
Solution 4
The entity manager used by spring data jpa ignores the fetch mode.
Use @EntityGraph annotation over Repository methods,
@EntityGraph(attributePaths = { "user", "hashtags"})
Page<LIPost> findByVoteTypeIn(Set<VoteType> listOfVotetype, Pageable paging);
Here user and hashtags are the properties in the LIPost entity.
The query build by spring data JPA uses left outer join to get the related entity(user and hashtags) data.
In this case, no need to use the annotation @NamedEntityGraph over the entity class.
Solution 5
"FetchType.LAZY
" will only fire for primary table. If in your code you call any other method that has a parent table dependency then it will fire query to get that table information. (FIRES MULTIPLE SELECT)
"FetchType.EAGER
" will create join of all table including relevant parent tables directly. (USES JOIN
)
When to Use:
Suppose you compulsorily need to use dependant parent table informartion then choose FetchType.EAGER
.
If you only need information for certain records then use FetchType.LAZY
.
Remember, FetchType.LAZY
needs an active db session factory at the place in your code where if you choose to retrieve parent table information.
E.g. for LAZY
:
.. Place fetched from db from your dao loayer
.. only place table information retrieved
.. some code
.. getCity() method called... Here db request will be fired to get city table info
Related videos on Youtube
SirKometa
Java enthusiast. Except for the programming I love photography, especially macro and portraits.
Updated on April 30, 2021Comments
-
SirKometa about 3 years
I do have a relation between three model object in my project (model and repository snippets in the end of the post.
When I call
PlaceRepository.findById
it does fire three select queries:("sql")
SELECT * FROM place p where id = arg
SELECT * FROM user u where u.id = place.user.id
SELECT * FROM city c LEFT OUTER JOIN state s on c.woj_id = s.id where c.id = place.city.id
That's rather unusual behavior (for me). As far as I can tell after reading Hibernate documentation it should always use JOIN queries. There is no difference in the queries when
FetchType.LAZY
changed toFetchType.EAGER
in thePlace
class (query with additional SELECT), the same for theCity
class whenFetchType.LAZY
changed toFetchType.EAGER
(query with JOIN).When I use
CityRepository.findById
suppressing fires two selects:SELECT * FROM city c where id = arg
SELECT * FROM state s where id = city.state.id
My goal is to have a the sam behavior in all situations (either always JOIN or SELECT, JOIN preferred though).
Model definitions:
Place:
@Entity @Table(name = "place") public class Place extends Identified { @Fetch(FetchMode.JOIN) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_user_author") private User author; @Fetch(FetchMode.JOIN) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "area_city_id") private City city; //getters and setters }
City:
@Entity @Table(name = "area_city") public class City extends Identified { @Fetch(FetchMode.JOIN) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "area_woj_id") private State state; //getters and setters }
Repositories:
PlaceRepository
public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom { Place findById(int id); }
UserRepository:
public interface UserRepository extends JpaRepository<User, Long> { List<User> findAll(); User findById(int id); }
CityRepository:
public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom { City findById(int id); }
-
Grigory Kislin over 7 yearsHava a look at 5 ways to initialize lazy relationsships: thoughts-on-java.org/…
-
SirKometa about 9 yearsI doesn't seem to work for me. I mean it works but... When I annotate repository with '@EntityGraph' it doesn't work itself (usually). For example: ` Place findById(int id);` works but
List<Place> findAll();
ends up with the Exceptionorg.springframework.data.mapping.PropertyReferenceException: No property find found for type Place!
. It works when I manually add@Query("select p from Place p")
. Seems like workaround though. -
wesker317 about 9 yearsMaybe it dosent work on findAll() since it's an existing method from the JpaRepository interface while your other method "findById" is a custom query method generated at runtime.
-
svlada about 9 yearsIS there way to achieve same result with Criteria API and Spring Data Specifications?
-
SirKometa about 9 yearsI've decided to mark this as the proper answer since it's the best. It is not perfect though. It works in most scenarios but so far I've noticed bugs in spring-data-jpa with more complex EntityGraphs. Thanks :)
-
Vlad Mihalcea about 9 yearsNot the fetch part, which requires JPA fetch profiles.
-
AndiDev over 8 years
@Query("select p from ManyToManyEntity p")
duplicates entities with many to many relationships that have more than one relation. However addingdistinct
,@Query("select distinct p from ManyToManyEntity p")
, seems to solve this issue. -
Ondrej Bozek almost 8 years
@EntityGraph
is almost ununsable in real scenarios since it cant be specified what kind ofFetch
we want to use (JOIN
,SUBSELECT
,SELECT
,BATCH
). This in combination with@OneToMany
association and makes Hibernate Fetch whole table to memory even if we use queryMaxResults
. -
Yan Khonski over 7 yearsVlad Mihalcea, could you share the link with an example how to do this by using Spring Data JPA criteria (specification)? Please
-
Vlad Mihalcea over 7 yearsI don't have any such example, but you can surely find one in Spring Data JPA tutorials.
-
JJ Zabkar over 7 yearsInterestingly, this answer got me on the right path to using
NamedEntityGraph
since I wanted a non-hydrated object graph. -
Ondrej Bozek about 7 yearsUnfortunately this doesn't work for queries generated using repository method name.
-
Homer1980ar almost 7 yearsI'm trying your solution but I have a private metadata variable in one of the methods to copy that is giving trouble. Can you share final code?
-
Eric Huang over 6 yearsif using query-time..... will you still need to define @OneToMany ...etc on the entity?
-
Vlad Mihalcea over 6 yearsThe mappings are driven by your app needs, not by the presence of a query.
-
antohoho almost 6 yearsrecursive Fetch doesn't work. If I have OneToMany it passes java.util.List to the next iteration
-
antohoho almost 6 yearsnot tested it well yet, but think should be something like this ((Join) descent).getJavaType() instead of field.getType() when call recursively applyFetchMode
-
Hoang Duc Nguyen over 5 years@adrhc no you dont understand, FetchMode and FetchType are two different things
-
Adrian over 5 yearsThanks, I wanted to say that JPQL queries may override the default fetching strategy with select fetch policy.
-
ASharma7 almost 5 years@VladMihalcea May you please look at this issue that I am facing regarding eager fetching while using JPA findBy methods . stackoverflow.com/questions/56248867/…
-
Clint Eastwood over 4 yearsthis answer deserves more upvotes. It's succinct and helped me a lot to understand why I was seeing lots of "magically triggered" queries...thanks a lot!
-
granadaCoder almost 4 years@NeilStockton FetchMode would be more evident if people would put the imports statements in their code samples. #endJavaRant
-
granadaCoder almost 4 yearscould you please add all the import statements? thank you.
-
Frischling over 2 yearsthis is the smallest change to make the fetchmode right, good tip! Spring docu is a bit vague on this, I find.
-
royatirek over 2 yearsOver time, I have understood that using @NamedEntityGraph over the entity and using the specification to make a query makes more sense as it keeps related things in one place.
-
Frischling over 2 yearsGood point; you'll define the default behaviour in
@NamedEntityGraph
, but if you need a specially crafted return for a function, you'd still use@EntityGraph
at the function, probably also naming the funtion to highlight this? I'd rather have a different return type/DTO in that case - that's something to think about.