[QueryDSL] return DTO

김형진·2023년 5월 11일
0

Spring Data를 통해 거의 해결이 되기 때문에 순수 JPA는 웬만해서는 쓸 일이 많지 않을 것 같고,,

정적 쿼리는 웬만한건 Spring Data JPA를 통해 편하게 처리할 수 있을 것 같다.(어차피 Spring Data JPA에서도 JPQL을 직접 쓸 수 있으니)

그래서 QueryDSL을 쓰는 일은 동적쿼리 외에는 쓸 일이 없을 것이라 생각했는데, 생각을 해보니 Spring Data JPA도 DTO를 바로 return해주는 재주는 없더라 (이게 왜 당연히 될 거라고 생각했는지..)

물론 JPQL을 통해 DTO클래스의 패키지 디렉토리를 알려주고 DTO의 new 연산자를 사용하면 DTO로 바로 return하는 것이 가능이야 하지만 실무에서 그렇게 쓸 일은 거의 없을 것 같다. 패키지가 달라져도 컴파일 에러가 나지 않고, 무엇보다 코드가 너무 지저분해 보이기 때문에

그래서 QueryDSL을 사용해야 하는 이유가 동적쿼리 말고 하나 더 생겼다.
순수 JPQL보다 DTO를 리턴하는 방법이 훨씬 쉽기 때문이다.

  1. DTO의 setter를 이용하는 방법
queryFactory
	.select(Projections.bean(MemberDto.class,
    	member.username,
        member.address))
    .from(member)
    .fetch();
  1. field 주입 방법
    (라이브러리를 이용해서 setter없이도 직접 주입이 가능하다고 한다. 실제로 setter, builder, constructor들을 모두 없애봤는데 객체 생성에 문제가 없다.)
queryFactory
	.select(Projections.fields(MemberDto.class,
    	member.username,
        member.address))
    .from(member)
    .fetch();

1번과 2번의 주의점은 entity의 필드명과 일치하는 dto의 필드에 데이터를 주입하기 때문에 필드명이 다르면 데이터를 제대로 얻을 수 없다는 것이다. (as로 alias를 dto의 필드와 일치하게 만들면 제대로 만들 수 있긴 하다.)

  1. dto의 constructor 이용하는 방법
queryFactory
	.select(Projections.constructor(MemberDto.class,
    	member.username,
        member.address))
    .from(member)
    .fetch();

아니면 DTO까지 Q타입 객체를 만들어 직접 사용하는 방법도 있다.

  1. @QueryProjection
// MemberDto

@QueryProjection
public MemberDto(String username, String address){
	this.username = username;
    this.address = address;
}
queryFactory
	.select(new QMember(member.username, memeber.address))
    .from(member)

DTO의 생성자 위에 @QueryProjection 어노테이션을 붙여주면 해당 생성자로 DTO를 생성해주는 Q타입 객체가 생성된다. (물론 컴파일 이후)
그 Qtype객체를 이용하면 바로 DTO를 생성해서 리턴해준다.

constructor를 사용한다는 점에서 3번과 같지만 다른 점은, Q타입 객체의 생성자를 곧바로 사용하기 때문에 파라미터가 생성자와 다르면 컴파일에러가 난다는 것이다.

3번은 해당 로직에서는 MemberDto의 생성자를 아직 모르기 때문에, 파라미터가 다르다면 runtime시점에 오류가 날 것이다.

각 방식은 일장일단이 있다.
1,2,3번의 방식은 에러가 나거나 못해도 데이터를 제대로 못받을 수 있는 코드임에도 컴파일 시점에는 알 수 없다. 안정성 측면에서 좋지 않다.
그래서 4번을 선택하자니 DTO가 QueryDSL에 직접 의존하게 된다는 문제가 발생한다.(@QueryProjection 어노테이션은 자바 표준이 아닌 QueryDSL에서 제공하는 어노테이션이다.) 아키텍처 측면에서 깔끔하지 못하게 된 것이다.

깔끔한 아키텍처가 더 중요하다고 느껴진다면 1,2,3번의 방식을 쓰면 된다.

개인적인 생각으로는

infra가 아닌 계층에서 외부에 의존하는 것은 있을 당연히 있을 수 없는 일이라고 생각한다. (누구라도 불편할 것이다)
application 로직에 외부의 객체가 돌아다닌다면 이후 기술이 변경됐을 때 코어로직이 무조건 수정되기 때문이다.
그러나 위 경우에는 어노테이션 하나에만 의존한다.
그것도 DTO객체가 의존하는 것이며, 만약 기술이 변경된다 하더라도 코어 로직의 수정 없이 어노테이션만 지우면 되기 때문에 이 경우엔 QueryDsl이 제공하는 어노테이션에 의존하는 것도 괜찮을 것이라 생각한다.

profile
히히

0개의 댓글