본문 바로가기
Spring/JPA

[Querydsl] @OneToMany에서 조건 절과 Fetch Join을 함께 사용할 때 서브쿼리 사용하기

by 흑시바 2024. 5. 12.

배경

프로젝트에서 동적 쿼리가 필요한 요구사항을 처리하기 위해 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에서 조건이 필요한 경우 해당 포스트처럼 서브 쿼리를 활용해 보도록 하자

댓글