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

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 설명필요한 경우

감사.



답변

초기화를 위해 게으른 컬렉션을 명시 적으로 호출해야합니다 (일반적인 방법은 .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의 페치 조인 절을 사용 하여 단일 라운드의 역할 연관을 데이터베이스에 간결하게로드하므로 위의 솔루션에서 두 개의 고유 한 쿼리로 인해 발생하는 성능 저하를 완화합니다.


답변

이 게시물은 오래된 게시물이지만 @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);

}


답변

몇 가지 옵션이 있습니다

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


답변

트랜잭션 내에서만 지연로드 할 수 있습니다. 따라서 저장소가있는 컬렉션에 액세스 할 수 있습니다.이 저장소에는 트랜잭션 get with association있습니다.


답변

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


답변

스프링 데이터 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();
    }
}

그게 다야!


답변

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

@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 ()를 사용하면 목록 자체를 가져 오지 않고 느리게 목록 화 된 경우 크기를 얻을 수 있습니다.