개발하는 햄팡이

[Spring][일정 관리 앱 만들기] CRUD 생성 - QueryDsl 쿼리문 코드 작성 본문

Back-End/Spring

[Spring][일정 관리 앱 만들기] CRUD 생성 - QueryDsl 쿼리문 코드 작성

hampangee 2025. 5. 24. 18:38

이전에 이어서 이제 내가 원하는 API를 구현할 차례이다.

 

전체 일정 조회를 하는 기능을 추가하려고 하는데 수정일과 작성자Id를 선택으로 받아 null인지 아닌지 체크하여 동적쿼리를 작성해야 한다.

나는 일정 페이지에서 -내가 작성한 OO일 일정들-을 보고싶어서 아래 API를 작성한 것이기 때문에 일치 여부를 판단하는 쿼리를 작성할 것이다.

 

 

 

저번에 구현체 클래스에 로직을 작성하면 된다고 했다.

 

그래서 구현체 클래스에 아래와 같은 쿼리문을 작성했다.

 

package com.sparta.schedule.schedule.repository;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.sparta.schedule.schedule.entity.QSchedule;
import com.sparta.schedule.schedule.entity.Schedule;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class ScheduleRepositoryCustomImpl implements ScheduleRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    @Override
    public List<Schedule> findSchedulesByUpdatedAtAndWriterId(LocalDate updatedAt, Long writerId) {
        QSchedule schedule = QSchedule.schedule;

        BooleanBuilder builder = new BooleanBuilder();

        if (updatedAt != null) {
            builder.and(schedule.updatedAt.eq(updatedAt));
        }

        if (writerId != null) {
            builder.and(schedule.user.id.eq(writerId));
        }

        return queryFactory.selectFrom(schedule)
                .where(builder)
                .fetch();
    }
}

 

 

 

 

일단 이렇게 하고 테스트하러 고고

 

파라미터를 넣든 말든 원하는 값이 잘 나온다.


 

전체 코드

Controller

@RestController
@RequestMapping("/schedules")
@RequiredArgsConstructor
public class ScheduleController {

    private final ScheduleService scheduleService;

    @PostMapping
    public ResponseEntity<ScheduleDto> createSchedule(@RequestBody CreateScheduleRequestDto request) {
        ScheduleDto response = scheduleService.createSchedule(
                request.getTitle(),
                request.getContents(),
                request.getWriterId()
        );

        return new ResponseEntity<>(response, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<ScheduleDto> getSchedule(@PathVariable Long id) {
        ScheduleDto response = scheduleService.getSchedule(id);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity<List<ScheduleDto>> getSchedules(@RequestParam(value = "updatedAt", required = false)LocalDate updatedAt,
                                                          @RequestParam(value = "writerId", required = false) Long writerId) {
        List<ScheduleDto> response = scheduleService.getSchedulesByUpdatedAtAndWriterId(updatedAt, writerId);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}

 

Service

@Service
@RequiredArgsConstructor
public class ScheduleService {

    private final UserRepository userRepository;
    private final ScheduleRepository scheduleRepository;

    public ScheduleDto createSchedule(String title, String contents, Long writerId) {
        Schedule newSchedule = new Schedule(title, contents);
        newSchedule.setUser(userRepository.findByIdOrElseThrow(writerId));
        Schedule saved = scheduleRepository.save(newSchedule);

        return ScheduleMapper.toDto(saved);
    }

    public ScheduleDto getSchedule(Long id) {
        Schedule findSchedule = scheduleRepository.findByIdOrElseThrow(id);
        return ScheduleMapper.toDto(findSchedule);
    }

    public List<ScheduleDto> getSchedulesByUpdatedAtAndWriterId(LocalDate updatedAt, Long writerId) {

        List<Schedule> result = scheduleRepository.findSchedulesByUpdatedAtAndWriterId(updatedAt, writerId);

        return result.stream()
                .map(ScheduleMapper::toDto)
                .collect(Collectors.toList());
    }
}

 

Repository

public interface ScheduleRepository extends JpaRepository<Schedule, Long>, ScheduleRepositoryCustom {

    default Schedule findByIdOrElseThrow(long id) {
        return findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."));
    }
}
public interface ScheduleRepositoryCustom {

    List<Schedule> findSchedulesByUpdatedAtAndWriterId(LocalDate updatedAt, Long writerId);
}
@Repository
@RequiredArgsConstructor
public class ScheduleRepositoryCustomImpl implements ScheduleRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    @Override
    public List<Schedule> findSchedulesByUpdatedAtAndWriterId(LocalDate updatedAt, Long writerId) {
        QSchedule schedule = QSchedule.schedule;

        BooleanBuilder builder = new BooleanBuilder();

        if (updatedAt != null) {
            builder.and(schedule.updatedAt.eq(updatedAt));
        }

        if (writerId != null) {
            builder.and(schedule.user.id.eq(writerId));
        }

        return queryFactory.selectFrom(schedule)
                .where(builder)
                .fetch();
    }
}

 

 


 

++

아직 페이징을 하고 있지 않지만 예전에 페이지네이션을 적용하는 쿼리를 작성했을땐 

AbstractQueryDslRepository 를 만들어 getPageImpl(query, pageable)메소드를 만들고 중복 코드를 줄였었다.

 

@RequiredArgsConstructor
public class AbstractQueryDslRepository {
    protected final JPAQueryFactory queryFactory;

    private final EntityManager entityManager;

    private Map<String, Querydsl> querydslMap = new HashMap<>();

    protected <T> Querydsl getQuerydsl(Class<T> clazz) {
        return querydslMap.computeIfAbsent(clazz.getName(), key -> {
            PathBuilder<T> builder = new PathBuilderFactory().create(clazz);
            return new Querydsl(entityManager, builder);
        });
    }

    protected <T> Page<T> getPageImpl(JPQLQuery<T> query, Pageable pageable) {
        long totalCount = query.fetchCount();
        List<T> results = getQuerydsl(query.getType()).applyPagination(pageable, query).fetch();
        return new PageImpl<>(results, pageable, totalCount);
    }
}

 

이런식으로 구현해서 구현체에 상속하면 계속 JPAQueryFactory나 EntityManager를 가져올 필요가 없어지고 Page처리를 하는 로직이 확 줄어든다.

 

아래는 예전에 작성했던 Repository이다.

public class UserRepositoryCustomImpl extends AbstractQueryDslRepository implements UserRepositoryCustom {

    public UserRepositoryCustomImpl(JPAQueryFactory queryFactory, EntityManager entityManager) {
        super(queryFactory, entityManager);
    }

    @Override
    public Page<User> findUsersByRoleId(String roleId, Pageable pageable) {
        QUser user = QUser.user;
        QUserRole userRole = QUserRole.userRole;

        JPQLQuery<User> query = from(user).join(user.userRoles, userRole)
                                          .where(userRole.roleId.eq(roleId))
                                          .select(user);

        return getPageImpl(query, pageable);
    }

    @Override
    public Page<User> getUsersByChannelId(String channelId, Pageable pageable) {
        // 입력받은 channelId 가 현재 유저가 참여한 채널 중 존재하는 channelId일 경우
        QUser user = QUser.user;
        QUserChannel userChannel = QUserChannel.userChannel;
        JPQLQuery<User> query = from(user)
                .join(user.userChannels, userChannel)
                .where(userChannel.channelId.eq(channelId));

        return getPageImpl(query, pageable);
    }
}

 

이렇게 하면 더 깔끔하게 할 수 있다.

나중에 페이지네이션이 요구사항으로 들어오면 위와 같이 구현할 예정이다.