Repository 계층의 반환 결과를 Service 계층에서 응답 객체로 매핑하는 코드를 일일이 작성하는 것이 번거롭고 효율적이지 못하다고 판단함
따라서 아래와 같이 map()을 통해 Repository 계층에서 쿼리 결과를 바로 매핑하도록 했다.
public List<TodoSearchResponse> findByTitleAndCreatedAtAndNickname(
String title, LocalDateTime startCreationTime, LocalDateTime endCreationTime, String nickname
) {
QTodo todo = QTodo.todo;
return queryFactory
.select(todo.title, todo.comments, todo.managers)
.from(todo)
.where(createDynamicBuilder(todo, title, startCreationTime, endCreationTime, nickname))
.fetch()
.stream()
.map(tuple -> new TodoSearchResponse(tuple.get(todo.title), tuple.get(todo.managers), tuple.get(todo.comments)))
.collect(Collectors.toList());
}
private BooleanBuilder createDynamicBuilder(
QTodo todo, String title, LocalDateTime startCreationTime, LocalDateTime endCreationTime, String nickname
) {
BooleanBuilder booleanBuilder = new BooleanBuilder();
if(title != null && !title.isEmpty()) { booleanBuilder.and(todo.title.contains(title)); }
if(startCreationTime != null) { booleanBuilder.and(todo.createdAt.after(startCreationTime)); }
if(endCreationTime != null) { booleanBuilder.and(todo.createdAt.before(endCreationTime)); }
if(nickname != null && !nickname.isEmpty()) { booleanBuilder.and(todo.user.nickname.contains(nickname)); }
return booleanBuilder;
}
하지만 아래와 같이 inner join이 암시적으로 실행되서 의도한 대로 검색이 이루어지지 않았음
select
t1_0.id,
t1_0.contents,
t1_0.created_at,
t1_0.modified_at,
t1_0.title,
t1_0.user_id,
t1_0.weather,
c2_0.id,
c2_0.contents,
c2_0.created_at,
c2_0.modified_at,
c2_0.todo_id,
c2_0.user_id,
m2_0.id,
m2_0.todo_id,
m2_0.user_id
from
todos t1_0
join
users u1_0
on u1_0.id=t1_0.user_id
join
comments c2_0
on t1_0.id=c2_0.todo_id
join
managers m2_0
on t1_0.id=m2_0.todo_id
where
u1_0.nickname like ? escape '!'
처음에는 left join을 명시하지 않아서 QueryDSL이 암시적으로 (inner) join을 수행하는 줄 알았다.
따라서 아래와 같이 left join을 명시적으로 추가하였다.
이 때 todo는 comment, manager를 각각 리스트로 참조하는 1:n 관계를 가졌고, 1 + N 문제가 발생하지는 않았으므로 fetchJoin은 사용하지 않았다.
public List<TodoSearchResponse> findByTitleAndCreatedAtAndNickname(
String title, LocalDateTime startCreationTime, LocalDateTime endCreationTime, String nickname
) {
QTodo todo = QTodo.todo;
return queryFactory
.select(todo.title, todo.comments, todo.managers)
.from(todo)
.leftJoin(todo.comments)
.leftJoin(todo.managers)
.where(createDynamicBuilder(todo, title, startCreationTime, endCreationTime, nickname))
.fetch()
.stream()
.map(tuple -> new TodoSearchResponse(tuple.get(todo.title), tuple.get(todo.managers), tuple.get(todo.comments)))
.collect(Collectors.toList());
}
private BooleanBuilder createDynamicBuilder(
QTodo todo, String title, LocalDateTime startCreationTime, LocalDateTime endCreationTime, String nickname
) {
BooleanBuilder booleanBuilder = new BooleanBuilder();
if(title != null && !title.isEmpty()) { booleanBuilder.and(todo.title.contains(title)); }
if(startCreationTime != null) { booleanBuilder.and(todo.createdAt.after(startCreationTime)); }
if(endCreationTime != null) { booleanBuilder.and(todo.createdAt.before(endCreationTime)); }
if(nickname != null && !nickname.isEmpty()) { booleanBuilder.and(todo.user.nickname.contains(nickname)); }
return booleanBuilder;
}
하지만 left join 추가 후에도 여전히 같은 문제가 발생했다.
select
t1_0.id,
t1_0.contents,
t1_0.created_at,
t1_0.modified_at,
t1_0.title,
t1_0.user_id,
t1_0.weather,
c2_0.id,
c2_0.contents,
c2_0.created_at,
c2_0.modified_at,
c2_0.todo_id,
c2_0.user_id,
m2_0.id,
m2_0.todo_id,
m2_0.user_id
from
todos t1_0
left join
comments c1_0
on t1_0.id=c1_0.todo_id
left join
managers m1_0
on t1_0.id=m1_0.todo_id
join
users u1_0
on u1_0.id=t1_0.user_id
join
comments c2_0
on t1_0.id=c2_0.todo_id
join
managers m2_0
on t1_0.id=m2_0.todo_id
where
u1_0.nickname like ? escape '!'
이후 추가적으로 QueryDSL에 대해 알아보니 select문의 todo.comments와 todo.managers 로 인해 암시적인 Join이 발생하는 것이었다.
또한 jpql과 마찬가지로 select()에서 DTO로 직접 매핑을 시킬 수 있었다.
이러한 사실들을 활용해 아래와 같이 코드를 수정하였다.
queryFactory
.select(Projections.constructor(TodoSearchResponse.class,
todo.title,
todo.comments.size(),
todo.managers.size()
)).from(todo)
.leftJoin(todo.comments)
.leftJoin(todo.managers)
.where(createDynamicBuilder(todo, title, startCreationTime, endCreationTime, nickname))
.fetch();
수정한 결과 의도대로 left join들만 수행되었다.
select
t1_0.id,
t1_0.contents,
t1_0.created_at,
t1_0.modified_at,
t1_0.title,
t1_0.user_id,
t1_0.weather,
c2_0.id,
c2_0.contents,
c2_0.created_at,
c2_0.modified_at,
c2_0.todo_id,
c2_0.user_id,
m2_0.id,
m2_0.todo_id,
m2_0.user_id
from
todos t1_0
left join
comments c1_0
on t1_0.id=c1_0.todo_id
left join
managers m1_0
on t1_0.id=m1_0.todo_id
where
u1_0.nickname like ? escape '!'