기존 @Query로 JPQL이나 nativeQuery를 사용했던 메소드를 QueryDsl을 통해 리팩토링했다.
(QueryDsl 진작 배울걸 정말 재밌다 😆)
@Query(value = "SELECT * FROM plant p" +
" INNER JOIN place pl ON p.place_id = pl.place_id" +
" LEFT JOIN watering w ON p.plant_id = w.plant_id" +
" WHERE p.gardener_id = :gardenerId AND w.plant_id IS NULL", nativeQuery = true)
List<Plant> findWaitingForWateringList(@Param("gardenerId") Long gardenerId);
식물 테이블에 물주기 테이블을 LEFT JOIN 후, 물주기 기록이 없는 식물을 받아오는 쿼리다.
@SpringBootTest
class PlantRepositoryTest {
@Autowired
PlantRepository plantRepository;
@Autowired
JPAQueryFactory queryFactory;
@Test
@DisplayName("물주기를 기다리는 식물들")
void findWaitingForWateringList() {
// 파라미터
long gardenerId = (long) 1;
// 원본 메소드
List<Plant> waitings = plantRepository.findWaitingForWateringList(gardenerId);
// 리팩토링 시작
List<Plant> waitingsTest = queryFactory
.selectFrom(plant)
.join(plant.place, place)
.leftJoin(plant.waterings, watering)
.where(plant.gardener.gardenerId.eq(gardenerId)
.and(watering.plant.plantId.isNull()))
.fetch();
// 비교하기위해 가공
List<Long> originList = waitings.stream().map(p -> p.getPlantId()).collect(Collectors.toList());
List<Long> refactoredList = waitingsTest.stream().map(p -> p.getPlantId()).collect(Collectors.toList());
// PK만 찍어보기
System.out.println(originList);
System.out.println(refactoredList);
Assertions.assertThat(originList.equals(refactoredList));
}
}
QueryDsl 코드를 작성 후, PK만 가공하여 리스트로 만든 다음 (콘솔에도 한 번 찍어보고) assert로 서로가 서로를 포함하고 있는지 검사했다.
결과는 성공😃!!!
@Query(value = "SELECT watering, chemical, plant" +
" FROM Watering watering" +
" LEFT JOIN Chemical chemical" +
" ON watering.chemical = chemical" +
" JOIN Plant plant" +
" ON watering.plant = plant" +
" WHERE plant.gardener.gardenerId = :gardenerId" +
" AND watering.wateringDate >= :startDate" +
" AND watering.wateringDate <= :endDate")
List<Watering> findAllWateringListByGardenerId(@Param("gardenerId") Long gardenerId, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
달력을 채우기 위해 시작일(startDate)과 종료일(endDate)을 구한 뒤 그 사이 날짜의 물주기를 가져오는 쿼리다.
wateringService 내의 메소드로 startDate/endDate을 구했는데, QueryDsl의 after(), before()로 날짜를 비교하기 위해 서비스 로직을 조금 수정했다.
기존이 23-08-27 <= date <= 23-10-07 조건이었다면,
지금은 23-08-26 < date < 23-10-08 조건이다.
@Test
@DisplayName("물주기 달력 메소드 - 통과")
void findAllWateringListByGardenerId() {
// 파라미터 만들기 - 기존 서비스 로직
LocalDate date = LocalDate.now();
LocalDate firstDayOfMonth = LocalDate.of(date.getYear(), date.getMonth(), 1);
LocalDate originStartDate = firstDayOfMonth.minusDays(firstDayOfMonth.getDayOfWeek().getValue() % 7);
int tmp = 42 - firstDayOfMonth.lengthOfMonth() - firstDayOfMonth.getDayOfWeek().getValue() % 7;
LocalDate originEndDate = firstDayOfMonth.plusDays(firstDayOfMonth.lengthOfMonth() - 1 + tmp);
// 원본 메소드
List<Watering> waterings = wateringRepository.findAllWateringListByGardenerId((long) 1, originStartDate, originEndDate);
// 리팩토링 시작
LocalDate startDate = wateringService.getStartDate(firstDayOfMonth);
LocalDate endDate = wateringService.getEndDate(firstDayOfMonth);
List<Watering> result = queryFactory
.selectFrom(watering)
.leftJoin(watering.chemical, chemical)
.join(watering.plant, plant)
.where(plant.gardener.gardenerId.eq((long) 1)
.and(watering.wateringDate.after(startDate))
.and(watering.wateringDate.before(endDate))
)
.orderBy(watering.wateringDate.asc())
.fetch();
// Id만 가공
List<Long> origin = waterings.stream().map(w -> w.getWateringId()).collect(Collectors.toList());
List<Long> refactored = result.stream().map(w -> w.getWateringId()).collect(Collectors.toList());
// 출력
System.out.println(origin);
System.out.println(refactored);
Assertions.assertThat(origin.equals(refactored));
}
역시 동일한 방법으로 Id만 추출하여 같은지 검사했다.
의존성을 굳이 늘리고 싶지 않아서 WateringRepositoryCustom
인터페이스를 생성한 뒤, WateringRepositoryCustomImpl
에서 QueryDsl 코드를 구현하고, 기존의 WateringRepository
가 JpaRepository<Watering, Long>, WateringRepositoryCustom
모두를 상속하도록 했다.
대충 이런 그림이다.
@Override
public List<Watering> findAllWateringListByGardenerId(Long gardenerId, LocalDate startDate, LocalDate endDate) {
return queryFactory
.selectFrom(watering)
.leftJoin(watering.chemical, chemical)
.fetchJoin() // ⭐️ LazyInitializationException
.join(watering.plant, plant)
.fetchJoin() // ⭐️ LazyInitializationException
.where(
plant.gardener.gardenerId.eq(gardenerId)
.and(watering.wateringDate.after(startDate))
.and(watering.wateringDate.before(endDate))
)
.orderBy(watering.wateringDate.asc())
.fetch();
}
응답용 DTO 생성에 필요하기 때문에 fetchJoin() 안해주면 LazyInitializationException~