본문 바로가기
Spring/JPA

[JPA] Hibernate 6.1 @OneToMany 일대다 조회 시 DISTINCT 자동 적용

by 델버 2023. 1. 25.

배경

  • 스프링 부트 3.0 부터 Hibernate 6.1로 기준이 되어 해당 버전을 사용하고 있었다.
  • 일대다에서 fetch join을 하면 중복 row(영한님 말로 - 뻥튀기)가 생긴다. 해결책으로 JSQL의 distinct를 적용하게되는데 SQL distinct와는 다르다.
  • SQL의 distinct는 row의 값이 모두 일치해야 제거가 된다. JPQL에서 distinct를 하면 SQL distinct 기능 + 중복 엔티티 제거하는 기능을 한다.
  • 하지만 Hibernate 6.0부터는 HQL(JPQL의 구현체)에 DISTINCT가 자동 적용된다.

프로젝트

  • 테스트를 위해 간단하게 DB 만들었다.

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String name;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

}
@Getter
@Entity
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members;

}
  • Team에서 일대다로 묶여있고 Member가 있다고 하자.
  • 만약 요청하는 클라이언트가 각 Team별로 회원 이름을 요청할 경우 어떻게 줘야할까?
// Repository
@Transactional
public List<Team> findAllTeam() {
    return em.createQuery("select t from Team t join fetch t.members", Team.class)
        .getResultList();
}
  • Repository에서는 join fetch를 사용하여 팀과 회원을 가져오게 된다.
  • 하지만 여기서 가져오는 것은 이렇다.

  • 데이터가 중복되어 가져오게 된다.

Hibernate 5.6.14

  • 스프링 부트 2.7.6 버전으로 확인하면 Hibernate 5.6.14 버전을 가져온다.
  • 이 버전으로 findAllTeam을 실행하여 출력하면 다음과 같이 나온다.

  • DB에서 본 것처럼 중복된 데이터가 나온다. 만약 그대로 응답할 경우 중복값을 응답하게 된다.
  • 여기서 쿼리에 DISTINCT를 추가하게 되면 우리가 원하는 값이 나올 것이다.
  • select distinct t from Team t join fetch t.members

Hibernate 6.0 ~ distinct 적용

  • 스프링 부트 3.0 부터 최소 기준이 Hibernate 6.1버전이 되었다.
  • Hibernate 6.1 환경에서 findAllTeam를 실행하면 다음과 같이 나온다.

  • DISTINCT를 적지 않아도 하이버네이트가 6.0 버전 부터는 쿼리에 추가하여 보낸다는 것이다. 또한 중복되는 엔티티가 있으면 하이버네이트가 필터링 해준다는 것이다.

주의

  • 주의할 점은 일단 SQL에서 중복된 값을 가져온 후 필터링하는 것이기 때문에 setFirstResult()나 setMaxResult()같이 페이징을 하면 중복된 row를 기준으로 하기 때문에 위험할 수 있다는 것이다.

'Spring > JPA' 카테고리의 다른 글

[JPA] fetch join 적용하기  (0) 2023.01.19

댓글