How FetchMode Works in Spring Data JPA

I have a relationship between the three model objects in my project (fragments of the model and the repository at the end of the post.

When I call PlaceRepository.findById , it launches three 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

This is a rather unusual behavior (for me). As far as I can tell from reading the Hibernate documentation, it should always use JOIN requests. There is no difference in queries when FetchType.LAZY changed to FetchType.EAGER in the Place class (query with optional SELECT), the same for the City class when FetchType.LAZY changed to FetchType.EAGER (query with JOIN).

When I use CityRepository.findById fire suppression, two choose:

  • SELECT * FROM city c where id = arg
  • SELECT * FROM state s where id = city.state.id

My goal is to have sam behavior in all situations (either always JOIN, or SELECT, JOIN is preferable).

Model Definitions:

A 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 } 

Storage:

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); } 
+42
java spring spring-data-jpa hibernate jpa
Apr 13 '15 at 9:48
source share
5 answers

I think Spring Data ignores FetchMode. I always use @NamedEntityGraph and @EntityGraph when working with Spring Data p>

 @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

+56
Apr 15 '15 at 13:28
source share
โ€” -

First of all, @Fetch(FetchMode.JOIN) and @ManyToOne(fetch = FetchType.LAZY) are antagonistic, one of which instructs EAGER, and the other offers LAZY fetch.

Expected sampling is rarely a good choice, and for predictable behavior, you'd better use the JOIN FETCH request time 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); } 
+24
Apr 16 '15 at 6:37
source share

Spring -jpa creates the query using the entity manager, and Hibernate will ignore the fetch mode if the query was created by the entity manager.

The following is the work I used:

  • Implement a custom repository that inherits from SimpleJpaRepository

  • Override the getQuery(Specification<T> spec, Sort sort) 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 fetch mode to force Hibernate to create a query with the correct connection.

    (Unfortunately, we need to copy the entire 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); } } } 
+11
08 Oct '15 at 4:36
source share

" FetchType.LAZY " will work only for the main table. If in the code you call any other method that has a dependency of the parent table, it will run a query to get this table information. (FIRES MULTIPLE SELECT)

" FetchType.EAGER " will create a join of the whole table, including the corresponding parent tables. (USES JOIN )

When to use: Suppose you definitely need to use a dependent parent table, then select FetchType.EAGER . If you only need information for specific records, use FetchType.LAZY .

Remember that FetchType.LAZY requires an active db factory session in the place of your code if you want to get parent table information.

eg. 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 

Sitelink

+2
Apr 16 '15 at 8:53
source share

I designed the dream83619 answer to handle the Hibernate @Fetch nested annotations. I used a recursive method to search for annotations in nested related classes.

So, you need to implement a custom repository and override the getQuery(spec, domainClass, sort) method. Unfortunately, you also need to copy all the related private methods: (.

Here is the code copied private methods omitted.
EDIT: Added remaining private methods.

 @NoRepositoryBean public class EntityGraphRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> { private final EntityManager em; protected JpaEntityInformation<T, ?> entityInformation; public EntityGraphRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); this.em = entityManager; this.entityInformation = entityInformation; } @Override protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) { CriteriaBuilder builder = em.getCriteriaBuilder(); CriteriaQuery<S> query = builder.createQuery(domainClass); Root<S> root = applySpecificationToCriteria(spec, domainClass, query); query.select(root); applyFetchMode(root); if (sort != null) { query.orderBy(toOrders(sort, root, builder)); } return applyRepositoryMethodMetadata(em.createQuery(query)); } private Map<String, Join<?, ?>> joinCache; private void applyFetchMode(Root<? extends T> root) { joinCache = new HashMap<>(); applyFetchMode(root, getDomainClass(), ""); } private void applyFetchMode(FetchParent<?, ?> root, Class<?> clazz, String path) { for (Field field : clazz.getDeclaredFields()) { Fetch fetch = field.getAnnotation(Fetch.class); if (fetch != null && fetch.value() == FetchMode.JOIN) { FetchParent<?, ?> descent = root.fetch(field.getName(), JoinType.LEFT); String fieldPath = path + "." + field.getName(); joinCache.put(path, (Join) descent); applyFetchMode(descent, field.getType(), fieldPath); } } } /** * Applies the given {@link Specification} to the given {@link CriteriaQuery}. * * @param spec can be {@literal null}. * @param domainClass must not be {@literal null}. * @param query must not be {@literal null}. * @return */ private <S, U extends T> Root<U> applySpecificationToCriteria(Specification<U> spec, Class<U> domainClass, CriteriaQuery<S> query) { Assert.notNull(query); Assert.notNull(domainClass); Root<U> root = query.from(domainClass); if (spec == null) { return root; } CriteriaBuilder builder = em.getCriteriaBuilder(); Predicate predicate = spec.toPredicate(root, query, builder); if (predicate != null) { query.where(predicate); } return root; } private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) { if (getRepositoryMethodMetadata() == null) { return query; } LockModeType type = getRepositoryMethodMetadata().getLockModeType(); TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type); applyQueryHints(toReturn); return toReturn; } private void applyQueryHints(Query query) { for (Map.Entry<String, Object> hint : getQueryHints().entrySet()) { query.setHint(hint.getKey(), hint.getValue()); } } public Class<T> getEntityType() { return entityInformation.getJavaType(); } public EntityManager getEm() { return em; } } 
+2
Aug 03 '16 at 12:18
source share



All Articles