그 어느 날과 같이 평범하게 개발을 하고 있던 날..
QueryDSL 에서 Projections 를 이용하여 DTO로 변환하는 과정을 개발하고 있었다.
그렇다.
오류가 발생했다.
class com.querydsl.core.types.QBean cannot access a member of class com.xxx(패키지명) with modifiers "protected"
대충 보아하니 접근제한자를 private 로 지정한 무언가에 대해 접근할 수 없어서 protected 로 변경하라는 거 같은데,
예상과 맞게도 문제의 원인은 다음과 같았다.
QueryDSL 프레임워크인 QBean이 클래스 com.xxx에서 접근수준이 protected 이상으로 선언된 멤버(필드 또는 메서드)에 접근하려고 하는데, 접근수준(접근제한자) 가 private로 되어 있어서 접근을 하지 못한 것 이었다.
그런데 왜 QueryDSL의 QBean이 접근수준이 private인 멤버에 접근하려고 한 것일까?
QueryDSL 구문을 짤때, 엔티티 자체로 반환하는 것을 막기 위해서 DTO로 변환해서 반환하곤 한다.
나도 그렇게 개발하고 있었고...
결론을 설명하기 전에, 엔티티를 DTO로 변환해주는 Projections 클래스에 대해서 알아보자.
먼저 QueryDSL에서 엔티티를 DTO로 변환하여 반환하는 방법은 Projections 클래스를 이용해서, 크게 3가지가 있다.
① Projections.bean(someDto.class, ...변환하고 싶은 setter가 존재하는 필드 나열)
즉, Setter 메서드를 통해서 접근하는 방법
import static com.querydsl.core.types.Projections.bean;
QBook qBook = QBook.book;
List<BookDTO> bookDTOs = queryFactory
.select(bean(BookDTO.class, qBook.title, qBook.author.name.as("authorName")))
.from(qBook)
.fetch();
② Projections.fields(someDto.class, ...변환하고 싶은 필드 나열)
즉, DTO의 필드명을 직접 지정하여 필드에 접근하는 방법
import static com.querydsl.core.types.Projections.fields;
QBook qBook = QBook.book;
List<BookDTO> bookDTOs = queryFactory
.select(fields(BookDTO.class, qBook.title, qBook.author.name.as("authorName")))
.from(qBook)
.fetch();
③ Projections.constructor(someDto.class, ...선언한 생성자의 필드 나열)
즉, DTO 클래스의 생성자를 호출하여 DTO 객체를 생성하는 방법
import static com.querydsl.core.types.Projections.constructor;
QBook qBook = QBook.book;
List<BookDTO> bookDTOs = queryFactory
.select(constructor(BookDTO.class, qBook.title, qBook.author.name.as("authorName")))
.from(qBook)
.fetch();
기본적으로 Projections는 인자 값이 없는 기본 생성자와 프로퍼티의 Getter/Setter의 접근을 반드시 필요로 하는데, 기본 생성자를 Protected 로 지정해버리면 해당 메서드에 접근할 수 없어 오류가 나는 것이다. (보통 getter/setter는 public 으로 지정하니, 문제가 없을 것이라 생각한다.)
결론
DTO 의 @NoArgsConstructor 는 접근레벨=PUBLIC 으로 선언하자.
엔티티를 선언할때, 기본생성자를 지정할때 PROTECTED로 선언하는 것이 보안상 안전하기 때문에 PROTECTED 로 선언하던 습관을 DTO에도 적용하니까 문제가 발생했던 것이다.
'Framework > Spring' 카테고리의 다른 글
[Spring AOP] ProxyFactory, 프록시 생성 공장 (2) | 2024.10.27 |
---|---|
[QueryDSL] 단건 조회 반환타입을 boolean으로 하는 좋은 방법 (0) | 2024.06.27 |
@ReqeustBody 바인딩시 boolean타입의 변수명 앞에 is를 붙이면 안되는 이유 (0) | 2024.06.26 |
Spring Security 란? (0) | 2024.05.12 |
[Spring] Model vs ModelMap (0) | 2024.04.01 |