스프링 컨트롤러에서 JPA 및 Hibernate와의 FetchType.LAZY 연관을 페치하는 방법


146

Person 클래스가 있습니다.

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Role> roles;
    // etc
}

게으른 다 대다 관계.

내 컨트롤러에는

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    PersonRepository personRepository;

    @RequestMapping("/get")
    public @ResponseBody Person getPerson() {
        Person person = personRepository.findOne(1L);
        return person;
    }
}

PersonRepository는 이 가이드 에 따라 작성된 코드입니다.

public interface PersonRepository extends JpaRepository<Person, Long> {
}

그러나이 컨트롤러에서는 실제로 지연 데이터가 필요합니다. 로딩을 어떻게 트리거 할 수 있습니까?

액세스하려고하면 실패합니다

no.dusken.momus.model.Person.roles 역할 컬렉션을 느리게 초기화하지 못했습니다. 프록시를 초기화 할 수 없습니다. 세션이 없습니다.

또는 내가 시도한 것에 따라 다른 예외.

XML 설명필요한 경우

감사.


Person매개 변수가 주어진 객체 를 가져 오기 위해 쿼리를 만드는 메서드를 작성할 수 있습니까 ? 그 안에 절을 Query포함 시키고 사람에게도 fetch로드하십시오 Roles.
SudoRahul

답변:


206

초기화를 위해 게으른 컬렉션을 명시 적으로 호출해야합니다 (일반적인 방법은 .size()이 목적 으로 호출 하는 것입니다). Hibernate에는 이것에 대한 전용 메소드가 Hibernate.initialize()있지만 JPA는 이에 상응하는 것이 없다. 물론 세션이 여전히 사용 가능할 때 호출이 완료되었는지 확인해야하므로 컨트롤러 메소드에 주석을 달십시오 @Transactional. 대안은 컨트롤러와 리포지토리간에 중간 서비스 계층을 생성하여 지연 수집을 초기화하는 메소드를 노출시킬 수 있습니다.

최신 정보:

위의 솔루션은 쉽지만 데이터베이스에 대한 두 가지 고유 한 쿼리 (하나는 사용자를위한 것이고 다른 하나는 역할을위한 것)입니다. 성능을 향상 시키려면 다음 방법을 Spring Data JPA 저장소 인터페이스에 추가하십시오.

public interface PersonRepository extends JpaRepository<Person, Long> {

    @Query("SELECT p FROM Person p JOIN FETCH p.roles WHERE p.id = (:id)")
    public Person findByIdAndFetchRolesEagerly(@Param("id") Long id);

}

이 메소드는 JPQL의 페치 조인 절을 사용 하여 단일 라운드의 역할 연관을 데이터베이스에 간결하게로드하므로 위의 솔루션에서 두 개의 고유 한 쿼리로 인해 발생하는 성능 저하를 완화합니다.


3
이 방법은 쉬운 솔루션이지만 데이터베이스에 대한 두 가지 고유 한 쿼리 (하나는 사용자를위한 것이고 다른 하나는 역할을위한 것)입니다. 성능을 향상 시키려면 JPQL 또는 Criteria API를 사용하여 다른 사람이 제안한대로 한 번에 사용자와 관련 역할을 열심히 가져 오는 전용 메소드를 작성하십시오.
zagyi

나는 호세의 대답에 대한 예를 물었다. 나는 완전히 이해하지 못한다는 것을 인정해야한다.
Matsemann

업데이트 된 답변에서 원하는 쿼리 방법에 대한 가능한 솔루션을 확인하십시오.
zagyi

7
흥미로운 점은 단순히을 사용 join하지 않으면 fetch세트가 반환됩니다 initialized = false. 따라서 세트에 액세스 한 후에도 여전히 두 번째 쿼리를 발행합니다. fetch관계가 완전히로드되고 두 번째 쿼리를 피하는 것이 중요합니다.
FGreg

페치 및 조인을 모두 수행 할 때의 문제점은 조인 술어 기준이 무시되고 목록 또는 맵의 모든 것을 얻는다는 것입니다. 모든 것을 원한다면 가져 오기를 사용하십시오. 특정한 것을 원하면 조인을 말하지만 조인은 비어 있습니다. 이것은 .LAZY 로딩을 사용하는 목적을 상실합니다.
K.Nicholas

37

이 게시물은 오래된 게시물이지만 @NamedEntityGraph (Javax Persistence) 및 @EntityGraph (Spring Data JPA) 사용을 고려하십시오. 조합이 작동합니다.

@Entity
@Table(name = "Employee", schema = "dbo", catalog = "ARCHO")
@NamedEntityGraph(name = "employeeAuthorities",
            attributeNodes = @NamedAttributeNode("employeeGroups"))
public class EmployeeEntity implements Serializable, UserDetails {
// your props
}

다음과 같이 봄 레포

@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String>           {

    @EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
    EmployeeEntity getByUsername(String userName);

}

1
참고 @NamedEntityGraph4.3.0 버전 전에 최대 절전 모드에서 구현되지 JPA 2.1 API의 일부입니다.
naXa

2
@EntityGraph(attributePaths = "employeeGroups")@NamedEntityGraph@Entity를 사용하지 않고도 메소드에 주석을 달기 위해 Spring Data Repository에서 직접 사용할 수 있습니다. 코드가 적고 저장소를 열 때 이해하기 쉽습니다.
Desislav Kamenov

13

몇 가지 옵션이 있습니다

  • RJ가 제안한대로 초기화 된 엔티티를 리턴하는 메소드를 저장소에 작성하십시오.

더 많은 작업, 최고의 성능.

  • OpenEntityManagerInViewFilter를 사용하여 전체 요청에 대해 세션을 열린 상태로 유지하십시오.

웹 환경에서 일반적으로 허용되는 작업이 줄어 듭니다.

  • 필요할 때 헬퍼 클래스를 사용하여 엔티티를 초기화하십시오.

OEMIV가 옵션이 아닌 경우 (예 : Swing 응용 프로그램) 유용하지만 저장소 구현에서 엔티티를 한 번에 초기화하는 데 유용 할 수도 있습니다.

마지막 옵션으로 유틸리티 클래스 JpaUtils를 작성했습니다. 일부 deph에서 엔티티를 초기화했습니다.

예를 들면 다음과 같습니다.

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}

모든 요청은 렌더링 등이없는 간단한 REST 호출이므로 트랜잭션은 기본적으로 전체 요청입니다. 입력 해 주셔서 감사합니다.
Matsemann

첫 번째는 어떻게합니까? 검색어를 작성하는 방법을 알고 있지만 귀하가하는 말을하는 방법은 없습니다. 예를 보여 주시겠습니까? 매우 도움이 될 것입니다.
Matsemann

zagyi는 그의 대답에서 모범을 보여 주었지만 어쨌든 나를 올바른 방향으로 지적 해 주셔서 감사합니다.
Matsemann

수업이 어떻게 진행 될지 모르겠습니다! 솔루션 낭비 다른 시간 낭비
Shady Sherif

전체 요청에 대해 세션을 열린 상태로 유지하려면 OpenEntityManagerInViewFilter를 사용하십시오. 엔티티에 대한 모든 콜렉션을 페치하도록 추가 요청을합니다.
Yan Khonski


6

뷰 렌더링 중에 세션을 열어 두려면 OpenSessionInViewFilter 가 필요하다고 생각합니다 (그러나 좋은 습관은 아닙니다).


1
JSP 또는 아무것도 사용하지 않고 REST-api를 만드는 것만으로 @Transactional이 도움이 될 것입니다. 그러나 다른 시간에는 유용 ​​할 것입니다. 감사.
Matsemann

@Matsemann 나는 지금 늦었다는 것을 알고 있습니다 ...하지만 컨트롤러에서도 OpenSessionInViewFilter를 사용할 수 있으며 응답이 컴파일 될 때까지 세션이 존재합니다 ...
Vishwas Shashidhar

@Matsemann 감사합니다! 거래 주석은 나를 위해 속임수를 썼다! 참고 : 나머지 클래스의 슈퍼 클래스에 주석을 달 경우에도 작동합니다.
desperateCoder

3

스프링 데이터 JpaRepository

스프링 데이터 JpaRepository는 다음 두 가지 방법을 정의합니다.

  • getOne하위 엔터티를 유지할 때 또는 부모 연결 을 설정하는 데 적합한 엔터티 프록시 를 반환합니다 .@ManyToOne@OneToOne
  • findById연관된 테이블에서 엔티티를로드하는 SELECT 문을 실행 한 후 엔티티 POJO를 리턴합니다.

그러나 귀하의 경우에, 당신은 전화 중 하나를하지 않았 getOne거나 findById:

Person person = personRepository.findOne(1L);

따라서 findOne메소드가에서 정의한 메소드 라고 가정 합니다 PersonRepository. 그러나이 findOne방법은 귀하의 경우에별로 유용하지 않습니다. Personwith with rolescollection 을 가져와야하므로 findOneWithRoles대신 메소드 를 사용하는 것이 좋습니다.

커스텀 스프링 데이터 메소드

PersonRepositoryCustom다음과 같이 인터페이스 를 정의 할 수 있습니다 .

public interface PersonRepository
    extends JpaRepository<Person, Long>, PersonRepositoryCustom { 

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}

그리고 다음과 같이 구현을 정의하십시오.

public class PersonRepositoryImpl implements PersonRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Person findOneWithRoles(Long id)() {
        return entityManager.createQuery("""
            select p 
            from Person p
            left join fetch p.roles
            where p.id = :id 
            """, Person.class)
        .setParameter("id", id)
        .getSingleResult();
    }
}

그게 다야!


쿼리를 직접 작성하고 @rakpan 답변의 EntityGraph와 같은 솔루션을 사용하지 않은 이유가 있습니까? 이것으로 동일한 결과가 나오지 않습니까?
Jeroen Vandevelde

EntityGraph를 사용하기위한 오버 헤드는 JPQL 쿼리보다 높습니다. 장기적으로는 쿼리를 작성하는 것이 좋습니다.
Vlad Mihalcea

오버 헤드를 정교하게 설명 할 수 있습니까 (어디에서 왔는가, 눈에 띄는가 ...)? 원인 둘 다 동일한 쿼리를 생성하는 경우 오버 헤드가 더 높은 이유를 이해하지 못합니다.
Jeroen Vandevelde

1
EntityGraphs 계획은 JPQL과 같이 캐시되지 않기 때문입니다. 성능이 크게 저하 될 수 있습니다.
Vlad Mihalcea

1
바로 그거죠. 시간이 있으면 기사를 써야합니다.
Vlad Mihalcea

1

다음과 같이 똑같이 할 수 있습니다 :

@Override
public FaqQuestions getFaqQuestionById(Long questionId) {
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    FaqQuestions faqQuestions = null;
    try {
        faqQuestions = (FaqQuestions) session.get(FaqQuestions.class,
                questionId);
        Hibernate.initialize(faqQuestions.getFaqAnswers());

        tx.commit();
        faqQuestions.getFaqAnswers().size();
    } finally {
        session.close();
    }
    return faqQuestions;
}

컨트롤러에서 faqQuestions.getFaqAnswers (). size ()를 사용하면 목록 자체를 가져 오지 않고 느리게 목록 화 된 경우 크기를 얻을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.