스프링 배치와 스케줄러를 사용한 코드를 작성하고 있다.
리더에서 사용하는 RepositoryItemReader는 paging을 지원해서 Page 타입을 리턴하는 리포지토리의 메소드를 사용하고 있다.
그런데, queryDsl을 이용한 다음 메소드를 리더에서 사용하면 저장이 무한으로 이뤄진다.
리더에서 읽은 객체는 2개인데 (id 6,7), 객체 6,7을 무한으로 프로세싱하고 저장한다.
@Repository
@RequiredArgsConstructor
public class RevenueRepositoryImpl implements RevenueRepositoryCustom {
private final JPAQueryFactory query;
// 현재 테스트 배치에 사용되는 page 타입 리턴하는 메소드
// 이번달 revenue 찾기
@Override
public Page<Revenue> findByDate2(LocalDate date, Pageable pageable) {
List<Revenue> fetch = query.select(revenue)
.from(revenue)
.where(revenue.date.year().eq(date.getYear()))
.where(revenue.date.month().eq(date.getMonthValue()))
.fetch();
// return new PageImpl(fetch);
// https://junior-datalist.tistory.com/342
JPAQuery<Long> count = query.select(revenue.count())
.from(revenue)
.where(revenue.date.year().eq(date.getYear()))
.where(revenue.date.month().eq(date.getMonthValue()));
return PageableExecutionUtils.getPage(fetch, pageable, count::fetchOne);
}
}
그런데 JPA를 사용한 메소드는 딱 2번만 원하는대로 저장된다.
public interface RevenueRepository extends JpaRepository<Revenue, Long>, RevenueRepositoryCustom {
Page<Revenue> findByDateAfter(LocalDateTime date, Pageable pageable);
}
두 메서드 다 같은 타입으로 같은 객체를 반환한다고 생각하는데 배치 결과가 다른 이유가 무엇일까.
findByDate에 페이징 관련 부분이 제대로 처리되지 않았기 때문! ->
findByDate에 offset과 limit 지정을 하지 않았다. . .
수정한 코드
@Override
public Page<Revenue> findByDate2(LocalDate date, Pageable pageable) {
List<Revenue> fetch = query.select(revenue)
.from(revenue)
.where(revenue.date.year().eq(date.getYear()))
.where(revenue.date.month().eq(date.getMonthValue()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
// return new PageImpl(fetch);
// https://junior-datalist.tistory.com/342
JPAQuery<Long> count = query.select(revenue.count())
.from(revenue)
.where(revenue.date.year().eq(date.getYear()))
.where(revenue.date.month().eq(date.getMonthValue()));
return PageableExecutionUtils.getPage(fetch, pageable, count::fetchOne);
}
query.offset은 시작 지점. 0부터 시작.
query.limit은 한 화면에 보여줄 데이터의 개수
findByDateAfter는 Spring Data JPA가 자동 생성란 쿼리로 동작하지만, findByDate는 직접 작성한 쿼리인데 제대로 처리되지 않았다.
이전에는 offset과 limit을 사용하지 않고 모든 데이터를 가져왔는데, 그렇다면 해당 페이지 데이터 제대로 구분 못하고 계속 같은 데이터 처리하게 됨. -> 무한반복
사실 offset, limit 없는 코드로 findByDate를 테스트 했을 때, 데이터가 제대로 2개만 읽혀서 processor나 writer에 문제가 있다고 의심하고 굉장히 많은 시간을 다른 곳에서 허비했다.
@DataJpaTest
@Import(TestQueryDslConfig.class)
@ExtendWith(MockitoExtension.class)
// h2 임베디드 모드 설정 말고 운영 DB 사용하겠다는 뜻
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class RevenueRepositoryTest {
@Autowired
TestEntityManager testEntityManager;
@Autowired
private ContractRepository contractRepository;
@Autowired
private RevenueRepository revenueRepository;
@PersistenceContext
EntityManager em;
@BeforeEach
void init() {
em = testEntityManager.getEntityManager();
}
@Test
public void testFindByDate() {
// 테스트할 날짜
LocalDate testDate = LocalDate.of(2024, Month.MARCH, 1);
// 페이징 정보 생성
Pageable pageable = PageRequest.of(0, 10);
// findByDate2 메서드 호출
Page<Revenue> resultPage = revenueRepository.findByDate(testDate, pageable);
resultPage.get().forEach(System.out::println);
// 결과 검증
assertNotNull(resultPage); // 결과 페이지가 null이 아닌지 확인
assertFalse(resultPage.isEmpty()); // 결과 페이지가 비어있지 않은지 확인
assertEquals(2, resultPage.getContent().size());
}
@Test
public void testFindByDateAfter() {
// 테스트할 기준 시간 설정
LocalDateTime testDateTime = LocalDateTime.of(2024, Month.MARCH, 1, 0, 0);
// 페이징 정보 생성
Pageable pageable = PageRequest.of(0, 10);
// findByDateAfter 메서드 호출
Page<Revenue> resultPage = revenueRepository.findByDateAfter(testDateTime, pageable);
// 결과 검증
assertNotNull(resultPage); // 결과 페이지가 null이 아닌지 확인
assertFalse(resultPage.isEmpty()); // 결과 페이지가 비어있지 않은지 확인
assertEquals(2, resultPage.getContent().size());
}
}
BUT 이 테스트는 맞지 않는 테스트 였음!!
전체를 읽으면 전체 개수인 2개가 나오는것이 당연한건데 이건 테스트 하는 의미가 없다.
페이징 테스트를 했어야 했음. 바보