2017-11-30 9 views
7

합리적인 시간과 적절한 쿼리 횟수로 DB에서 매우 복잡한 개체를 완전히로드하는 데 문제가 있습니다.최대 절전 모드 : 복잡한 개체의 초기화

내 객체가 포함 된 엔티티을 많이 가지고, 각각의 개체가 다른 개체에 대한 참조를 가지고, 다른 엔티티 참조 등등 그래서

(그래서, 중첩 수준이 6 인), I가 예를 만든 또 다른 및 내가 원하는 것을 보여주십시오 : https://github.com/gladorange/hibernate-lazy-loading

나는 사용자가 있습니다.

사용자는 @OneToMany 좋아하는 것의 컬렉션 오렌지, 사과, 포도 품종 및 복숭아. 각 포도 나무에는 포도의 모음이 @OneToMany 있습니다. 각 과일은 단 하나의 String 필드가있는 다른 엔티티입니다.

나는 각 유형의 30 가지 좋아하는 과일로 사용자를 만들고 있으며, 각 포도 나무에는 10 가지 포도가 있습니다. 그래서, 전적으로 DB - 30 * 4 과일, 100 * 30 포도 및 한 사용자에 421 개체를 보유하고 있습니다.

그리고 원하는 것 : 6 개 이상의 SQL 쿼리를 사용하여로드하려고합니다. 그리고 각 쿼리는 큰 결과 집합을 생성해서는 안됩니다 (큰 경우 결과 집합이 200 개가 넘음).

  • 6 요청 :

    나의 이상적인 솔루션은 다음이 될 것입니다. 첫 번째 요청은 결과 세트의 사용자와 크기에 대한 정보를 결과 세트의 사용자와 크기에 대한 사과에 대한 1

  • 두 번째 요청 반환 정보가 세 번째 (30)

  • , 네 번째와 다섯 번째 요청이 동일한 반환입니다 반환 , 두 번째로 (결과 세트 크기 = 30) Grapevines, Oranges 및 Peaches에 사용됩니다.

  • 여섯 번째 요청이 SQL 세계에서 매우 간단 ALL 포도

을 위해 포도를 반환하지만 나는 JPA (최대 절전 모드)와 같은 달성 할 수 없다.

  1. 사용이 from User u join fetch u.oranges ...처럼, 가입 가져 오기 :

    나는 다음과 같은 방법을 시도했다. 이것은 끔찍한 일입니다. 결과 집합은 30 * 30 * 30 * 30이고 실행 시간은 10 초입니다. 요청 수 = 3. 포도없이 시험해 보았습니다. 포도로 결과 세트의 x10 크기를 얻을 수 있습니다.

  2. 그냥 지연로드를 사용하십시오. 이 예에서 가장 좋은 결과입니다 (@ Fetch = 포도에 대해서는 SUBSELECT 임). 그러나이 경우 각 요소 컬렉션을 수동으로 반복해야합니다. 또한 subselect 가져 오기가 너무 전역 설정이므로 쿼리 수준에서 작동 할 수있는 무언가를 갖고 싶습니다. 이상적인 결과 집합과 시간. 6 쿼리 및 43 밀리 초.

  3. 엔티티 그래프로로드 중. 페치 조인 (fetch join)과 동일하지만 모든 포도에 grapevine을 요청합니다. 그러나 결과 시간은 더 좋지만 (6 초), 여전히 끔찍합니다. 요청 건수> 30.

  4. 별도의 쿼리에서 엔티티의 "수동"로드로 JPA를 속이려고했습니다. 개체를 수동으로 로딩 (I 연관된로드를 포함하여이 쿼리를 완벽하게 제어 할 첫 번째 쿼리 : 마찬가지로 : 그것은 각 수집을위한 두 개의 쿼리를 필요로하기 때문에

     
    SELECT u FROM User where id=1; 
    SELECT a FROM Apple where a.user_id=1; 
    

이, 게으른 로딩이 조금 더 나쁘다 Hibernate에 의해 동일한 엔티티를 lazy-load하는 두 번째 질의 (최대 절전 모드에 의해 자동으로 실행 됨)

실행 시간은 52, 쿼리 수 = 10 (사용자의 경우 1, 포도의 경우 1, 각 과일 컬렉션)

실제로 "수동"solut 이온을 SUBSELECT fetch와 함께 사용하면 "간단한"페치 조인을 사용하여 하나의 쿼리 (예 : @OneToOne 엔티티)에서 필요한 엔티티를로드 할 수 있으므로 사용하겠습니다. 하지만 컬렉션을로드하기 위해 두 가지 쿼리를 수행해야한다는 점이 마음에 들지 않습니다.

제안 사항?

+0

나는 수동으로 5 개의 쿼리를 작성한다 : 먼저 열거 형'@ OneToMany'를 사용하여 두 번째 쿼리와 결합한 다음 설명 된 요청 3-6 개 중 하나를 사용하여 Java 코드로 개체를 어셈블한다. 물론 너무 우아하지는 않지만,'@ OneToMany' 곱셈이나 게으른 로딩은 필요하지 않습니다. –

+0

@RomanPuchkovskiy 예, 이해가됩니다. 그러나로드 및 편집 후에도 인스턴스를 저장할 수 있어야합니다. 그래서 작동 할 수도 있지만 때로는 저장 중에 "예외를 찾을 수 없습니다"와 같은 예외가 발생합니다 (이전 컬렉션에서 엔티티를 검색하려고하기 때문에). 그래서 저는 수동으로 객체를 생성하지 않기로 결정했습니다. 그러나 저는 귀하의 조언이 읽기 전용 작업에 적합해야한다고 생각합니다. – EvilOrange

답변

3

한 번에. 대신 ? 그레이프 바인 ID가 전달됩니다. 이는 한 번에 1 List<Grape> 콜렉션을 쿼리하는 것과 반대입니다.

그건 당신의 무기고에 대한 또 다른 기술입니다.

+0

그러나'Gravevine'을 30 개 적재했는데 그 중 하나의 포도에만 관심이 있다면 그 포도를 적재하면 다른 포도를 위해 포도를 적재하게됩니다. 29. '포도원'.. 만약 당신이보고 싶은 사람이 3 개의 포도를 가지고 있고, 다른 하나가 10 만명이라면, 실제로 필요한 것보다 더 많은 양을로드하게 될 것입니다. – Tobb

0

여기 귀하의 요구 사항을 이해하지 못합니다. 그것은 당신이 Hibernate가하도록 설계되지 않은 것을하려고하고, 할 수 없을 때 Hibernate가 최적이 아닌 해킹 솔루션을 원한다고 생각합니다. 제한을 완화하고 효과가있는 것을 사지 않겠습니까? 왜 이런 제약이 있죠?

일부 일반 포인터 : 최대 절전/JPA를 사용하는 경우

  1. , 당신은 쿼리를 제어하지 않습니다. 당신은 어느 쪽이든 (약간 예외로)에 가정되지 않는다. 얼마나 많은 질의들, 그들이 실행되는 순서 등은 당신의 통제 범위를 훨씬 벗어납니다. 쿼리를 완벽하게 제어하려면 JPA를 건너 뛰고 대신 JDBC를 사용하십시오 (예 : Spring JDBC).
  2. 이러한 유형의 상황에서 결정을 내리는 데있어서 게으름로드를 이해하는 것이 중요합니다. 지연 엔티티 관계는 이 아니고 소유 엔티티를 가져올 때이 반입되지 않으며, 대신 Hibernate는 데이터베이스로 돌아가서 실제로 사용될 때 가져옵니다. 즉, 매번 속성을 사용하지 않으면 지연로드가 발생하지만 실제 사용 시간에는 페널티가 발생합니다. (페치 조인은 게으른 릴레이션을 열망하는 데 사용됩니다. 데이터베이스의 일반로드에서는 사용하지 않아도됩니다.)
  3. 하이버 네이트를 사용하는 쿼리 최적화는 첫 번째 작업 라인이 아니어야합니다. 항상 데이터베이스로 시작하십시오. 기본 키와 외래 키, 일반 양식 등으로 올바르게 모델링 되었습니까? 적절한 위치 (일반적으로 외래 키)에 검색 색인이 있습니까?
  4. 매우 제한된 데이터 집합에서 성능을 테스트하면 최상의 결과를 얻지 못할 수도 있습니다. 연결 등으로 인한 오버 헤드가있을 수 있으며 실제로 쿼리를 실행하는 데 소요되는 시간보다 커집니다. 또한 몇 밀리 초의 비용이 소요되는 무작위 hickup이있을 수 있으며 이는 오도 된 결과를 줄 것입니다.
  5. 코드를 자세히 보지 못했습니다. 엔티티의 콜렉션에 대한 설정 도구를 제공하지 마십시오. 실제로 트랜잭션 내에서 호출된다면, Hibernate는 예외를 던질 것이다.
  6. tryManualLoading은 아마도 생각보다 많은 것을 할 것입니다. 먼저, 사용자를 (게으른 로딩으로) 불러 와서 각각의 과일을 가져온다. 그리고 lazy-loading을 통해 과일을 다시 가져온다. (Hibernate가 쿼리가 느린 로딩과 같을 때 쿼리가 동일하다는 것을 이해하지 않는 한)
  7. 게으른 로딩을 시작하기 위해 실제로 전체 콜렉션을 루프 할 필요는 없다. user.getOranges().size() 또는 Hibernate.initialize(user.getOranges())을 사용할 수 있습니다.포도는 모든 포도를 초기화하기 위해 반복해야합니다. 적절한 데이터베이스 설계하고 올바른 장소에서 게으른 로딩와

는,이 이외의 다른 필요하지 않아야 :

em.find(User.class, userId); 

그리고 어쩌면 게으른 부하가있는 경우 쿼리를 가져 가입 많은 시간이 걸린다.

내 경험상 최대 절전 모드 속도를 높이는 가장 중요한 요소는 데이터베이스의 검색 인덱스 입니다. Grape의 많은 컬렉션을 가져 in (?, ?, etc)를 사용하는 것이이 일을-선택 하위

@OneToMany 
@BatchSize(size = 30) 
private List<Grape> grapes = new ArrayList<>(); 

을 대신하는 일을 : 나는 게으르게 포도 나무에서 포도의 컬렉션을 가져 오는 방법에 대한 또 다른 옵션을 제안거야

5

일반적으로 이러한 엔터티 및 컬렉션 모두에 대해 batch fetching을 사용하여 이러한 사용 사례의 99 %를 처리합니다. 가져온 엔티티를 읽은 동일한 트랜잭션/세션에서 처리하면 처리 로직에서 필요로하는 연관을 탐색하기 만하면 생성 된 쿼리가 매우 최적화됩니다. 받는 사람까지 (마지막 명령이 하지 실제로 여러으로, grapes 컬렉션을 각 포도 나무에 대한 쿼리를 실행하는 것입니다

User user = entityManager.find(User.class, userId); 
Hibernate.initialize(user.getOranges()); 
Hibernate.initialize(user.getApples()); 
Hibernate.initialize(user.getGrapevines()); 
Hibernate.initialize(user.getPeaches()); 
user.getGrapevines().forEach(grapevine -> Hibernate.initialize(grapevine.getGrapes())); 

참고 : 가져온 개체를 반환하려면 분리로, 당신은 수동으로 협회 초기화 지정된 @BatchSize)은 첫 번째를 초기화 할 때 초기화됩니다. 모든 것을 초기화하여 모든 것이 초기화되도록 할 수 있습니다.

이 기법은 수동 접근법과 비슷하지만 더 효율적이다. (쿼리는 각 콜렉션에 대해 반복되지 않는다.) 내 생각에는 읽기 쉽고 유지할 수있다. (자동으로 하이버 네이트가 생성하는 동일한 쿼리를 수동으로 작성하는 대신 Hibernate.initialize를 호출하면된다) .