이번 기능은 메인페이지에서 사용자의 공복혈당과 최근 혈당을 반환해야 했습니다.
아래 메인페이지의 상단 빨간 부분에 대한 api라고 생각하면 되겠습니다.

아래 데이터베이스를 확인하면 측정 시간이 문자열로 저장이 됩니다.
첫 번째 api는 데이터베이스에서 공복 이라고 저장된 수치를 뽑지만, 오늘 날짜이어야 합니다.
두 번째 api는 오늘 날짜중에서 가장 최근의 수치를 가져와야합니다.

그럼 어떤 식으로 데이터를 가져와야 할까요?
저는 QueryDsl를 활용하여 간단하게 데이터를 가져올 수 있었습니다.

저는 QueryDsl를 사용하기 위해 혈당 리포지토리를 분리했습니다.
분리한 이유는 Repository Interface와 Custom 구현체를 분리하여 관리하기 위함입니다.
이를 통해서 코드의 유지보수 및 확장성을 높일 수 있다고 판단했습니다.
그 밖에도, 커스텀 쿼리 확장성 제공(복잡한 쿼리 추가할 때 로직 추가 가능),단일 책임 원칙 준수, 유지보수하기 용이하여 나중에 새로운 쿼리가 필요하면 BloodSugarRepositoryCustomImpl 추가할 수 있으며, 기본 기능과 충돌없이 사용이 가능하여 쉽게 관리가 가능합니다.
-BloodSugarRepository-
기본적인 crud 기능 제공
public interface BloodSugarRepository extends JpaRepository<BloodSugar,Long>,BloodSugarRepositoryCustom {
}
-BloodSugarRepositoryCustom-
커스텀 리포지토리 인터페이스로, 기본 JPA 기능으로는 처리하기 힘든 복잡한 쿼리나 비즈니스 로직을 처리
public interface BloodSugarRepositoryCustom {
Optional<BloodSugar> findFastingBloodSugar(Long userId);
Optional<BloodSugar> findClosestBloodSugar(Long userId);
// 이 부분은 다른 포스팅에서 설명하도록 하겠습니다.
List<BloodSugar>findTodayBloodSugar(Long userId, LocalDate date);
}
-BloodSugarRepositoryCustomImpl-
@Service
public class BloodSugarRepositoryCustomImpl implements BloodSugarRepositoryCustom {
@Autowired
private JPAQueryFactory jpaQueryFactory;
@Override
public Optional<BloodSugar> findFastingBloodSugar(Long userId) {
QBloodSugar bloodSugar = QBloodSugar.bloodSugar;
LocalDate today = LocalDate.now();
LocalDateTime start = today.atStartOfDay();
LocalDateTime end = today.atTime(LocalTime.MAX);
return Optional.ofNullable(jpaQueryFactory
.selectFrom(bloodSugar)
.where(bloodSugar.user.id.eq(userId)
.and(bloodSugar.measurementTime.eq("공복"))
.and(bloodSugar.create_At.between(start,end)))
.fetchOne());
}
@Override
public Optional<BloodSugar> findClosestBloodSugar(Long userId) {
QBloodSugar bloodSugar = QBloodSugar.bloodSugar;
// 쿼리 작성: 현재 시간과 가장 가까운 혈당 기록을 찾기 위해 시간을 기준으로 정렬
BloodSugar closestBloodSugar = jpaQueryFactory
.selectFrom(bloodSugar)
.where(bloodSugar.user.id.eq(userId))
.orderBy(bloodSugar.create_At.desc()) // 시간을 오름차순으로 정렬
.fetchFirst(); // 가장 가까운 하나의 혈당 기록만 가져오기
return Optional.ofNullable(closestBloodSugar);
}
• findFastingBloodSugar(Long userId) : 특정 사용자의 공복 혈당 기록을 조회합니다. 이 메서드는 사용자의 ID와, 측정 시간이 “공복”인 데이터를 조회하며, 오늘 날짜의 범위 안에서 데이터를 찾습니다.
• findClosestBloodSugar(Long userId) : 사용자의 가장 최근 혈당 기록을 조회합니다. 혈당 기록을 내림차순으로 정렬하여 가장 최근 데이터를 반환합니다.
처음에는 가장 최근의 혈당을 구하기 위해 많은 하드코딩을 했습니다. 그러나 저장된 데이터를 정렬하여 가장 최근의 데이터를 가져올 수 있을 것 같았습니다.
-getUserInfoMainPage-
위 디자인을 보면 알 수 있듯이, 데이터는 유저이름,유저 프로필 이미지, 공복혈당, 최근혈당을 반환해야합니다. dto에 userId를 포함한 필요 데이터들을 정의하고 반환했습니다.
@Override
public MainPageGetUserDto getUserInfoMainPage(HttpServletRequest request) {
String token = jwtProvider.resolveToken(request);
String email = jwtProvider.getUsername(token);
log.info("[token] : {}", token);
log.info("[email] : {}", email);
User user = userRepository.getByEmail(email);
log.info("[user] : {}", user);
// 사용자 공복 혈당
Optional<BloodSugar> fastingBloodSugar = bloodSugarRepository.findFastingBloodSugar(user.getId());
String fastingBloodSugarLevel = fastingBloodSugar
.map(BloodSugar::getBloodSugarLevel)
.orElse("공복 혈당을 찾을 수 없습니다.");
// 현재 시간과 가장 가까운 혈당
Optional<BloodSugar> currentBloodSugarLevel = bloodSugarRepository.findClosestBloodSugar(user.getId());
String currentBloodSugar = currentBloodSugarLevel
.map(BloodSugar::getBloodSugarLevel)
.orElse("최근에 등록된 혈당을 찾을 수 없습니다.");
// 생성자로 객체 생성
return new MainPageGetUserDto(
user.getId(),
user.getUserName(),
user.getProfileUrl(),
fastingBloodSugarLevel,
currentBloodSugar
);
}
서비스 로직은 다음과 같습니다. 예외처리를 더 강화할 것입니다.이번 포스팅에서는 쿼리 Dsl 위주로 봐주시면 감사하겠습니다.

이렇게 결과가 잘 반환되는 것을 확인하였습니다!
프로필사진은 회원가입때 넣지 않아서 null인점 참고부탁드립니다.
QueryDsl 공부는 많이했지만, 막상 처음에는 생각이 나지 않아서 하드 코딩을 진행했습니다. 기능은 되었지만 코드가 매우 복잡했고, 길었습니다. 어떻게 하면 코드의 가독성과 재사용성을 올릴 수 있을까에 대한 의문점을 QueryDsl로 해결했습니다.
앞으로 복잡한 쿼리문에 대한 개발도 충분히 가능할 것 같습니다!