본문 바로가기
Spring

[Spring] Querydsl Select 성능 개선

by 가드 2022. 11. 22.
728x90

1. 테스트 테이블 관계 생성

@Entity
public class Shop {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long shopNo;
    private String name;
    @OneToOne(mappedBy = "shop")
    private License license;
}
@Entity
public class License {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long licenseNo;
    private String name;
    @OneToOne
    @JoinColumn(name = "shop_id")
    private Shop shop;
}

Shop과 License 테이블은 @OneToOne 관계이며 테스트 데이터는 1000만건 기준으로 진행해보겠다.

2. 조회 쿼리의 성능 문제점

public List<Shop> toEntity(Long shopNo) {
    return jpaQueryFactory.selectFrom(shop)
        .where(shop.shopNo.eq(shopNo))
        .fetch();
}

위의 해당 쿼리는 어떠한 문제가 있을까?

Entity를 조회하기 때문에 Hibernate 캐시가 적용되고 불필요한 컬럼 조회 OneToOne의 N+1 문제가 발생된다.

모든 컬럼의 데이터와 연계된 테이블의 데이터가 필요한 경우가 아님에도 불구하고 조회가 발생하게 된다.

어떻게 쿼리를 변경을 해야 조회 성능이 더 좋아질까?

3. 성능 개선 방법

3-1. Projection & DTO 사용

Projection을 이용하여 필요한 컬럼만 조회하게 하며 Entity를 리턴하지 말고 Dto로 리턴되게 하면 된다.

public List<ShopDto> toDto(Long shopNo) {
    return jpaQueryFactory
        .select(Projections.fields(ShopDto.class, shop.id, shop.name, shop.shopNo))
        .from(shop)
        .where(shop.shopNo.eq(shopNo))
        .fetch();
}

Entity 조회 쿼리 로그

Entity로 조회하니 모든 컬럼과 관계에 대한 조회가 발생되어 N+1이 되어 버렸다. (참고로 @OneToOne은 LazyLoading이 안된다.)

DTO 조회 쿼리 로그

DTO를 이용하니 필요한 컬럼 조회 후 종료되었다. Entity의 데이터 컬럼이 많고 대량의 데이터를 조회한다고 하면 Entity 조회 방식은 분명 성능에 안좋은 영향을 발생시킬 것이다.

3-2. 최소한으로 컬럼 조회

위에서 DTO 방식에서 조금 더 합리적으로 개선시켜보자.

shop.id, shop.name, shop.shopNo 컬럼을 조회하게 했는데 생각해보면 where 절에 shopNo 조건을 줬다는 건 이미 shopNo 데이터를 가지고 있다는 것이다. 그렇다면 조회 컬럼에 shopNo는 제외해도 되지 않을까?

public List<ShopDto> toDtoWithOutShopNo(Long shopNo) {
    return jpaQueryFactory
        .select(Projections.fields(ShopDto.class, shop.id, shop.name, 
        					Expressions.asNumber(shopNo).as("shopNo")))
        .from(shop)
        .where(shop.shopNo.eq(shopNo))
        .fetch();
}

shopNo를 Expressions.asNumber(shopNo).as("shopNo")로 변환하였다. as 표현은 SELECT 절에서 제외가 되며 가지고 있는 값을 그대로 대입하게 된다. asString, asEnum, asDate, asTime, asDateTime, asBoolean등 많은 as 표현을 할 수 있으니 컬럼 정의에 맞춰서 선언해주면 된다.

이렇게 선언하면 Database를 통해 10개 컬럼 조회 -> 3 ~ 4개 컬럼 조회로 이점을 챙길 수 있다.

 

개인적인 생각

무조건 DTO 방식을 사용하자라는 내용이 아니다.  실시간으로 Entity 변경이 필요한 경우에는 당연 Entity 조회 방식을 사용해야 한다. 그러나 성능 개선 또는 대량의 데이터 조회 건이 필요할 경우, Entity 조회 성능이 생각보다 나오지 않는 경우 DTO 조회 방식을 검토하자라는 말을 하고 싶다.

 

300x250

댓글