Hibernate Criteria는 FetchType.EAGER를 사용하여 여러 번 자식을 반환합니다.


115

Order목록 이있는 클래스가 있고 다음 OrderTransactions과 같이 일대 다 Hibernate 매핑으로 매핑했습니다.

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}

이러한 에는 다음 기준으로 필터링하는 데 사용되는 Order필드도 있습니다 orderStatus.

public List<Order> getOrderForProduct(OrderFilter orderFilter) {
    Criteria criteria = getHibernateSession()
            .createCriteria(Order.class)
            .add(Restrictions.in("orderStatus", orderFilter.getStatusesToShow()));
    return criteria.list();
}

이것은 효과가 있으며 결과는 예상대로입니다.

이제 내 질문은 다음과 같습니다. 페치 유형을 명시 적으로로 설정 EAGER하면 Orders가 결과 목록에 여러 번 표시 되는 이유는 무엇 입니까?

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public List<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}

새 설정으로 동일한 결과에 도달하려면 기준 코드를 어떻게 변경해야합니까?


1
아래에서 무슨 일이 일어나는지보기 위해 show_sql을 활성화 해 보셨습니까?
Mirko N.

OrderTransaction 및 Order 클래스 코드도 추가하십시오. \
Eran Medan

답변:


115

구성을 올바르게 이해 한 경우 실제로 예상되는 동작입니다.

Order모든 결과에서 동일한 인스턴스를 얻지 만 지금부터는OrderTransaction 하므로 일반 SQL 조인이 반환하는 것과 동일한 양의 결과를 반환해야합니다.

그래서 실제로는 해야 여러 번 apear. 이것은 저자 (Gavin King) 자신이 여기 에서 매우 잘 설명합니다. 두 가지 모두 이유와 여전히 뚜렷한 결과를 얻는 방법을 설명합니다.


Hibernate FAQ 에서도 언급되었습니다 .

Hibernate는 컬렉션에 대해 외부 조인 가져 오기가 활성화 된 쿼리에 대해 고유 한 결과를 반환하지 않습니다. (구분 키워드를 사용하더라도)? 먼저 SQL과 SQL에서 OUTER JOIN이 작동하는 방식을 이해해야합니다. SQL의 외부 조인을 완전히 이해하고 이해하지 못하는 경우이 FAQ 항목을 계속 읽지 말고 SQL 설명서 또는 자습서를 참조하십시오. 그렇지 않으면 다음 설명을 이해하지 못하고 Hibernate 포럼에서이 동작에 대해 불평 할 것입니다.

동일한 Order 개체의 중복 참조를 반환 할 수있는 일반적인 예 :

List result = session.createCriteria(Order.class)
                    .setFetchMode("lineItems", FetchMode.JOIN)
                    .list();

<class name="Order">
    ...
    <set name="lineItems" fetch="join">

List result = session.createCriteria(Order.class)
                       .list();
List result = session.createQuery("select o from Order o left join fetch o.lineItems").list();

이러한 모든 예는 동일한 SQL 문을 생성합니다.

SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID

중복 항목이있는 이유를 알고 싶으십니까? SQL 결과 집합을 보면 Hibernate는 외부 조인 결과의 왼쪽에 이러한 중복을 숨기지 않고 구동 테이블의 모든 중복을 반환합니다. 데이터베이스에 5 개의 주문이 있고 각 주문에 3 개의 라인 항목이있는 경우 결과 집합은 15 개의 행이됩니다. 이러한 쿼리의 Java 결과 목록에는 모두 Order 유형의 15 개 요소가 있습니다. 5 개의 Order 인스턴스 만 Hibernate에 의해 생성되지만 SQL 결과 집합의 중복은이 5 개의 인스턴스에 대한 중복 참조로 보존됩니다. 이 마지막 문장을 이해하지 못하는 경우 Java 및 Java 힙의 인스턴스와 그러한 인스턴스에 대한 참조 간의 차이점을 읽어야합니다.

(왼쪽 외부 조인이 필요한 이유는 무엇입니까? 라인 항목이없는 추가 주문이있는 경우 결과 집합은 오른쪽을 채우는 NULL이있는 16 행이됩니다. 여기서 라인 항목 데이터는 다른 주문에 대한 것입니다. 광고 항목이 없습니다. 그렇지 않은 경우 HQL에서 내부 조인 가져 오기를 사용합니다.

Hibernate는 기본적으로 이러한 중복 참조를 필터링하지 않습니다. (당신이 아닌) 어떤 사람들은 실제로 이것을 원합니다. 어떻게 필터링 할 수 있습니까?

이렇게 :

Collection result = new LinkedHashSet( session.create*(...).list() );

121
다음 설명을 이해하더라도 Hibernate 포럼에서이 동작에 대해 불평 할 수 있습니다. 어리석은 동작을 뒤집기 때문입니다!
Tom Anderson

17
옳은 톰, 이드 개 빈스에 대해 잊혀진 오만한 태도. 그는 또한 'Hibernate는 기본적으로 이러한 중복 참조를 필터링하지 않습니다. 어떤 사람들은 (당신이 아닌) 실제로 이것을 원한다고 이드는 사람들이 실제로 이것을 개미 할 때 관심을 가질 것입니다.
Paul Taylor

16
@TomAnderson 맞습니다. 왜 그 중복이 필요한 사람이 있습니까? 난 아무 생각이 없기 때문에 당신이 원하는대로 ... 당신은 그들 중 많은 사람들로, 중복을 직접 만들 수 있습니다, 순수한 호기심 요구하고 .. ;-)
Parobay

13
한숨. 이것은 실제로 Hibernate 결함, IMHO입니다. 쿼리를 최적화하고 싶으므로 매핑 파일에서 "select"에서 "join"으로 이동합니다. 갑자기 내 코드가 곳곳에서 깨집니다. 그런 다음 결과 변환기와 기타 사항을 추가하여 모든 DAO를 실행하고 수정합니다. 사용자 경험 == 매우 부정적입니다. 나는 어떤 사람들이 기괴한 이유로 중복을 갖는 것을 절대적으로 좋아한다는 것을 이해합니다. 그러나 fetch = "justworkplease"를 지정하여 "이러한 객체를 더 빨리 가져 오지만 중복으로 나를 괴롭히지 마십시오"라고 말할 수없는 이유는 무엇입니까?
Roman Zenka 2015 년

@Eran : 나는 비슷한 종류의 문제에 직면하고 있습니다. 중복 된 부모 개체를 얻지 못하지만 응답에 부모 개체 수가 많은만큼 반복되는 각 부모 개체의 자식을 얻습니다. 이 문제가 왜 발생했는지 아십니까?
mantri

93

Eran이 언급 한 것 외에도 원하는 동작을 얻는 또 다른 방법은 결과 변환기를 설정하는 것입니다.

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

8
이것은 Criteria를 사용하여 2 개의 컬렉션 / 연결을 가져 오려고 할 때를 제외하고 대부분의 경우에 작동합니다.
JamesD

42

시험

@Fetch (FetchMode.SELECT) 

예를 들면

@OneToMany(targetEntity = OrderTransaction.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Fetch (FetchMode.SELECT)
public List<OrderTransaction> getOrderTransactions() {
return orderTransactions;

}


11
FetchMode.SELECT는 Hibernate에 의해 실행되는 SQL 쿼리 수를 증가 시키지만 루트 엔티티 레코드 당 하나의 인스턴스 만 보장합니다. Hibernate는이 경우 모든 자식 레코드에 대해 선택을 실행합니다. 따라서 성능 고려 사항과 관련하여이를 고려해야합니다.
Bipul 2014

1
@BipulKumar 예,하지만 하위 개체에 액세스하기 위해 지연 가져 오기를위한 세션을 유지해야하기 때문에 지연 가져 오기를 사용할 수없는 경우이 옵션입니다.
mathi

18

List 및 ArrayList를 사용하지 말고 Set 및 HashSet을 사용하십시오.

@OneToMany(targetEntity = OrderTransaction.class, cascade = CascadeType.ALL)
public Set<OrderTransaction> getOrderTransactions() {
    return orderTransactions;
}

2
이것은 Hibernate 모범 사례에 대한 우연한 언급입니까 아니면 OP의 다중 자식 검색 질문과 관련이 있습니까?
Jacob Zwiers 2014 년


알았다. OP의 질문에 이차입니다. 그러나 dzone 기사는 아마도 저자가 의견을 인정한 바에 따라 약간의 소금과 함께 가져와야 할 것입니다.
Jacob Zwiers 2014

2
이것은 IMO에 대한 아주 좋은 대답입니다. 중복을 원하지 않는 경우 목록보다 세트를 사용하는 것이 좋습니다. 세트 사용 (물론 올바른 equals / hascode 메소드 구현)이 문제를 해결했습니다. redhat 문서에 명시된대로 해시 코드 / 같음을 구현할 때 id 필드를 사용하지 않도록주의하십시오.
Mat

1
IMO에 감사드립니다. 또한 equals () 및 hashCode () 메서드를 만드는 데 어려움을 겪지 마십시오. IDE 또는 Lombok에서 생성하도록하십시오.
Αλέκος

3

Java 8 및 Streams를 사용하여 유틸리티 메서드에 다음 반환 문을 추가합니다.

return results.stream().distinct().collect(Collectors.toList());

스트림은 중복을 매우 빠르게 제거합니다. 다음과 같이 Entity 클래스에서 주석을 사용합니다.

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "STUDENT_COURSES")
private List<Course> courses;

내 앱에서 데이터베이스의 데이터가 필요한 방법으로 세션을 사용하는 것이 더 낫다고 생각합니다. 완료되면 세션을 종료합니다. 물론 leasy fetch 유형을 사용하도록 Entity 클래스를 설정합니다. 리팩토링하러갑니다.


3

2 개의 관련 컬렉션을 가져 오는 데 동일한 문제가 있습니다. 사용자는 2 개의 역할 (Set)과 2 개의 식사 (List)를 가지며 식사가 중복됩니다.

@Table(name = "users")
public class User extends AbstractNamedEntity {

   @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
   @Column(name = "role")
   @ElementCollection(fetch = FetchType.EAGER)
   @BatchSize(size = 200)
   private Set<Role> roles;

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
   @OrderBy("dateTime DESC")
   protected List<Meal> meals;
   ...
}

DISTINCT는 도움이되지 않습니다 (DATA-JPA 쿼리).

@EntityGraph(attributePaths={"meals", "roles"})
@QueryHints({@QueryHint(name= org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")}) // remove unnecessary distinct from select
@Query("SELECT DISTINCT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);

마침내 두 가지 해결책을 찾았습니다.

  1. LinkedHashSet으로 목록 변경
  2. "meal"필드 만있는 EntityGraph를 사용하고 선언 된대로 역할을로드하는 LOAD를 입력합니다 (N + 1 문제를 방지하기 위해 EAGER 및 BatchSize = 200 기준).

마지막 해결책:

@EntityGraph(attributePaths = {"meals"}, type = EntityGraph.EntityGraphType.LOAD)
@Query("SELECT u FROM User u WHERE u.id=?1")
User getWithMeals(int id);

1

다음과 같은 해킹을 사용하는 대신 :

  • Set 대신에 List
  • criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

SQL 쿼리를 수정하지 않는 경우 (JPA 사양 인용) 사용할 수 있습니다.

q.select(emp).distinct(true);

결과 SQL 쿼리를 수정하므로 포함 DISTINCT됩니다.


0

외부 조인을 적용하고 중복 된 결과를 가져 오는 것은 훌륭한 동작이 아닙니다. 남은 유일한 해결책은 스트림을 사용하여 결과를 필터링하는 것입니다. 더 쉬운 필터링 방법을 제공하는 java8에게 감사드립니다.

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