배경
프로젝트에서 동적 쿼리가 필요한 요구사항을 처리하기 위해 Querydsl를 사용하게 되었다. 쿼리 호출을 최소화하기 위해 1:N 연관관계로 되어있는 데이터를 페치 조인(fetchJoin)으로 가져오려고 했으나, 생각대로 되지 않았다. 필요한건 연관된 목록 전체의 정보인데, 조건에 맞는 데이터 한 건만 가지고 오게 되는 것이었다.
아래는 동일한 상황을 간단하게 재연한 것이다.
Shiba
@Entity
@Table(name = "tb_shiba_holic")
@Getter
public final class Shiba {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "seq")
private Long seq;
@OneToMany(mappedBy = "shiba")
private List<Item> itemList = new ArrayList<>();
}
Item
@Entity
@Table(name = "tb_shiba_item")
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "seq")
private Long seq;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shiba_seq")
private Shiba shiba;
}
Shiba 엔티티와 Item 엔티티는 1:N, N:1로 양방향 연관관계를 맺고 있다.
Repository
public class ShibaRepositorySupportImpl extends QuerydslRepositorySupport implements ShibaRepositorySupport {
public ShibaRepositorySupportImpl() {
super(Shiba.class);
}
@Override
public List<Shiba> findAllByItemName(String name) {
return from(shiba)
.leftJoin(shiba.itemList, item).fetchJoin()
.where(item.name.contains(name))
.distinct()
.fetch();
}
}
조건절에서 OneToMany 관계에 있는 Item의 이름을 조회한다.
Test
@Test
void findAllItemName() {
// given
Shiba savedShiba1 = shibaRepository.save(new Shiba());
createItem("WHITE_SHIBA", savedShiba1);
createItem("BLACK_SHIBA", savedShiba1);
createItem("RED_SHIBA", savedShiba1);
Shiba savedShiba2 = shibaRepository.save(new Shiba());
createItem("WHITE_SHIBA", savedShiba2);
createItem("GREEN_SHIBA", savedShiba2);
createItem("YELLOW_SHIBA", savedShiba2);
entityManager.flush();
entityManager.clear();
// when
List<Shiba> result = shibaRepository.findAllByItemName("WHITE_SHIBA");
// then
System.out.println(result);
}
private void createItem(String name, Shiba savedShiba) {
Item item1 = Item.builder()
.name(name)
.shiba(savedShiba)
.build();
itemRepository.save(item1);
}
테스트 코드를 통해 테스트를 수행한다.
Shiba 엔티티에 Item 엔티티를 3개 씩 넣었지만, 막상 조회되는 건 조건절에 해당하는(name = "WHITE_SHIBA") 데이터 1개 뿐이다.
요구사항에서 Shiba 엔티티 정보만 필요하다고 하면 문제가 없다. 하지만 요구사항에서 OneToMany 관계에 있는 모든 엔티티 정보를 조회해야 한다고 하면 어떻게 해야 할까?
원인
Shiba.seq | Item.seq | Item.name | Item.shiba_seq |
1 | 1 | WHITE_SHIBA | 1 |
1 | 2 | BLACK_SHIBA | 1 |
1 | 3 | RED_SHIBA | 1 |
2 | 4 | WHITE_SHIBA | 2 |
2 | 5 | GREEN_SHIBA | 2 |
2 | 6 | YELLOW_SHIBA | 2 |
1개의 데이터만 나오는 원인은 전체 데이터 중 상단 표에서 조건에 해당하는 빨간색으로 처리된 데이터만 페치 조인 되기 때문이다.
이런 상황에서 Shiba 엔티티와 연관된 모든 Item을 가지고 오려면 어떻게 해야 할까?
해결
이런 경우 서브 쿼리를 사용해서 문제를 해결할 수 있다.
@Override
public List<Shiba> findAllByItemName(String name) {
JPQLQuery<Shiba> subQuery = from(shiba)
.leftJoin(shiba.itemList, item)
.where(item.name.contains(name));
return from(shiba)
.leftJoin(shiba.itemList, item).fetchJoin()
.where(shiba.in(subQuery))
.distinct()
.fetch();
}
OneToMany에서 조건 조회해야 하는 부분을 서브 쿼리로 빼내서 조회하면 원하는 결과를 얻는 것을 확인할 수 있다.
테스트 결과로 필요한 데이터 3개를 모두 가져오는 것을 확인 할 수 있다.
OneToMany에서 조건이 필요한 경우 해당 포스트처럼 서브 쿼리를 활용해 보도록 하자
'Spring > JPA' 카테고리의 다른 글
JPA 효과적인 양방향 연관관계 구성방법 (0) | 2024.10.16 |
---|---|
JPA Buddy 플러그인을 이용해서 엔티티에 equals와 hashcode를 명시적으로 구현해야 하는 이유 (0) | 2024.06.22 |
@Lob 사용시 DB와 엔티티 필드 타입이 다른 경우 발생하는 문제와 해결방법 (0) | 2024.01.07 |
JPA @Lob 이해하기 (with MariaDB) (0) | 2024.01.06 |
JPA에서 Lombok 사용시 주의할 점 (0) | 2023.12.30 |
댓글