복붙노트

[SPRING] 스프링 데이터 JPA에서 FetchMode는 어떻게 작동합니까?

SPRING

스프링 데이터 JPA에서 FetchMode는 어떻게 작동합니까?

내 프로젝트의 세 모델 객체 (모델 및 저장소 스 니펫은 게시물의 끝 부분에 있음) 사이의 관계가 있습니다.

PlaceRepository.findById를 호출하면 세 가지 선택 쿼리가 실행됩니다.

( "sql")

그것은 오히려 비정상적인 행동입니다 (나를 위해). Hibernate 문서를 읽은 후에는 항상 JOIN 쿼리를 사용해야한다. FetchType.LAZY가 Place 클래스의 FetchType.EAGER (추가 SELECT가 포함 된 쿼리)로 변경된 경우 쿼리에 차이가 없습니다. FetchType.LAZY가 FetchType.LAGZ (JOIN 쿼리)로 변경되었을 때 City 클래스와 동일합니다.

CityRepository.findById를 사용하면 두 가지를 선택하지 않습니다.

내 목표는 모든 상황 (언제나 JOIN 또는 SELECT, JOIN 선호)에서 sam 동작을 사용하는 것입니다.

모델 정의 :

장소:

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

시티:

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

저장소 :

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);
}

해결법

  1. ==============================

    1.스프링 데이터는 FetchMode를 무시한다고 생각합니다. 스프링 데이터로 작업 할 때 항상 @NamedEntityGraph 및 @EntityGraph 주석을 사용합니다.

    스프링 데이터는 FetchMode를 무시한다고 생각합니다. 스프링 데이터로 작업 할 때 항상 @NamedEntityGraph 및 @EntityGraph 주석을 사용합니다.

    @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);
    
    }
    

    여기에서 설명서를 확인하십시오.

  2. ==============================

    2.무엇보다도 @Fetch (FetchMode.JOIN)와 @ManyToOne (fetch = FetchType.LAZY)은 적대적입니다. 하나는 EAGER 페칭을 지시하고 다른 하나는 LAZY 페치를 제안합니다.

    무엇보다도 @Fetch (FetchMode.JOIN)와 @ManyToOne (fetch = FetchType.LAZY)은 적대적입니다. 하나는 EAGER 페칭을 지시하고 다른 하나는 LAZY 페치를 제안합니다.

    Eager 페칭은 거의 좋은 선택이 아니며 예측 가능한 동작을 위해 query-time JOIN FETCH 지시어를 사용하는 것이 좋습니다.

    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);
    }
    
  3. ==============================

    3.Spring-jpa는 엔티티 관리자를 사용하여 질의를 생성하고, Hibernate는 질의가 엔티티 관리자에 의해 구축 된 경우 페치 모드를 무시할 것이다.

    Spring-jpa는 엔티티 관리자를 사용하여 질의를 생성하고, Hibernate는 질의가 엔티티 관리자에 의해 구축 된 경우 페치 모드를 무시할 것이다.

    다음은 내가 사용했던 해결 방법이다 :

  4. ==============================

    4."FetchType.LAZY"는 기본 테이블에서만 실행됩니다. 코드에서 부모 테이블 종속성이있는 다른 메서드를 호출하면 해당 테이블 정보를 얻기 위해 쿼리가 실행됩니다. (FIRES MULTIPLE SELECT)

    "FetchType.LAZY"는 기본 테이블에서만 실행됩니다. 코드에서 부모 테이블 종속성이있는 다른 메서드를 호출하면 해당 테이블 정보를 얻기 위해 쿼리가 실행됩니다. (FIRES MULTIPLE SELECT)

    "FetchType.EAGER"는 관련 상위 테이블을 포함한 모든 테이블의 조인을 직접 생성합니다. (USES JOIN)

    사용시기 : 종속 부모 테이블 정보를 강제로 사용해야하고 FetchType.EAGER를 선택한다고 가정합니다. 특정 레코드에 대한 정보 만 필요하면 FetchType.LAZY를 사용하십시오.

    FetchType.LAZY는 부모 테이블 정보를 검색하도록 선택한 경우 코드의 위치에 활성 db 세션 팩토리가 필요하다는 것을 기억하십시오.

    예 : 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
    

    추가 참조

  5. ==============================

    5.나는 내포 된 Hibernate @Fetch 어노테이션을 처리하도록 dream83619에 대한 정교함을 정교했다. 재귀 적 메서드를 사용하여 중첩 된 관련 클래스에서 주석을 찾습니다.

    나는 내포 된 Hibernate @Fetch 어노테이션을 처리하도록 dream83619에 대한 정교함을 정교했다. 재귀 적 메서드를 사용하여 중첩 된 관련 클래스에서 주석을 찾습니다.

    따라서 사용자 정의 저장소를 구현하고 getQuery (spec, domainClass, sort) 메소드를 대체해야합니다. 불행히도 참조 된 모든 개인 메소드를 복사해야합니다.

    다음은 코드입니다. 복사 된 개인 메소드는 생략됩니다. 편집 : 나머지 개인 방법을 추가했습니다.

    @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;
        }
    }
    
  6. ==============================

    6.Vlad Mihalcea (https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/ 참조)에 따르면 :

    Vlad Mihalcea (https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/ 참조)에 따르면 :

    JPQL 쿼리가 선언 된 페칭 전략을 무시할 수 있으므로 일부 참조 된 엔터티를 열심히로드하거나 단순히 EntityManager를 사용하여 ID를로드하기 위해 조인 가져 오기를 사용해야합니다 (가져 오기 전략을 따르지 만 사용자의 유스 케이스).

  7. ==============================

    7.http://jdpgrailsdev.github.io/blog/2014/09/09/spring_data_hibernate_join.html 이 링크에서

    http://jdpgrailsdev.github.io/blog/2014/09/09/spring_data_hibernate_join.html 이 링크에서

    만일 당신이 Hibernate 위에 JPA를 사용한다면, Hibernate에 의해 사용 된 FetchMode를 JOINHowever로 설정하는 방법이 없다. 당신이 Hibernate 위에 JPA를 사용한다면, Hibernate에 의해 사용되는 FetchMode를 JOIN으로 설정할 방법이 없다.

    Spring Data JPA 라이브러리는 생성 된 쿼리의 동작을 제어 할 수있는 도메인 기반 설계 사양 API를 제공합니다.

    final long userId = 1;
    
    final Specification<User> spec = new Specification<User>() {
       @Override
        public Predicate toPredicate(final Root<User> root, final 
         CriteriaQuery<?> query, final CriteriaBuilder cb) {
        query.distinct(true);
        root.fetch("permissions", JoinType.LEFT);
        return cb.equal(root.get("id"), userId);
     }
    };
    
    List<User> users = userRepository.findAll(spec);
    
  8. ==============================

    8.인출 모드는 객체를 ID로 선택하는 경우에만 작동합니다 (예 : entityManager.find () 사용). Spring Data는 항상 질의를 생성하기 때문에 가져 오기 모드 설정은 아무 쓸모가 없습니다. 가져 오기 조인과 함께 전용 쿼리를 사용하거나 엔티티 그래프를 사용할 수 있습니다.

    인출 모드는 객체를 ID로 선택하는 경우에만 작동합니다 (예 : entityManager.find () 사용). Spring Data는 항상 질의를 생성하기 때문에 가져 오기 모드 설정은 아무 쓸모가 없습니다. 가져 오기 조인과 함께 전용 쿼리를 사용하거나 엔티티 그래프를 사용할 수 있습니다.

    최상의 성능을 원할 때는 실제로 필요한 데이터의 서브 세트 만 선택해야합니다. 이를 위해 일반적으로 불필요한 데이터를 가져 오지 않으려면 DTO 방식을 사용하는 것이 좋지만 일반적으로 JPQL을 통해 DTO 모델을 구성하는 전용 쿼리를 정의해야하기 때문에 오류가 자주 발생하는 상용구 코드가 많이 발생합니다. 생성자 표현식.

    스프링 데이터 예측은 여기에서 도움이 될 수 있지만, 어느 시점에서 Blaze-Persistence Entity Views와 같은 솔루션이 필요합니다.이 뷰를 사용하면 훨씬 쉽게이 기능을 활용할 수 있습니다. 엔티티 당 DTO 인터페이스를 생성하면 getter가 필요한 데이터의 하위 집합을 나타냅니다. 문제의 해결책은 다음과 같을 수 있습니다.

    @EntityView(Identified.class)
    public interface IdentifiedView {
        @IdMapping
        Integer getId();
    }
    
    @EntityView(Identified.class)
    public interface UserView extends IdentifiedView {
        String getName();
    }
    
    @EntityView(Identified.class)
    public interface StateView extends IdentifiedView {
        String getName();
    }
    
    @EntityView(Place.class)
    public interface PlaceView extends IdentifiedView {
        UserView getAuthor();
        CityView getCity();
    }
    
    @EntityView(City.class)
    public interface CityView extends IdentifiedView {
        StateView getState();
    }
    
    public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
        PlaceView findById(int id);
    }
    
    public interface UserRepository extends JpaRepository<User, Long> {
        List<UserView> findAllByOrderByIdAsc();
        UserView findById(int id);
    }
    
    public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {    
        CityView findById(int id);
    }
    

    면책 조항, 저는 블레이즈 - 퍼시스턴스의 저자입니다. 그래서 편향 될 수 있습니다.

  9. from https://stackoverflow.com/questions/29602386/how-does-the-fetchmode-work-in-spring-data-jpa by cc-by-sa and MIT license