인프런 커뮤니티 질문&답변
jdbc 커서, 페이징에서 일대다 관계 데이터 뻥튀기 조회 처리 방법 질문
작성
·
54
·
수정됨
1
강의 섹션4. 데이터베이스를 지배하라. 챕터에서 아래는 JPA 방식으로 일대다 관계를 가진 데이터를 페치 조인으로 가져오는 예제 코드야.
returnnewJpaCursorItemReaderBuilder<Post>()
.name("postBlockReader")
.entityManagerFactory(entityManagerFactory)
.queryString(""" SELECT p FROM Post p JOIN FETCH p.reports r WHERE r.reportedAt >= :startDateTime AND r.reportedAt < :endDateTime """)
.parameterValues(Map.of( "startDateTime", startDateTime, "endDateTime", endDateTime )) .build();
근데 jdbc 커서 예제에서는 킬구형이 아래처럼 단일 테이블만 조회하는 예제를 사용했어.
@Bean publicJdbcCursorItemReader<Victim> terminatedVictimReader() {
returnnew JdbcCursorItemReaderBuilder<Victim>()
.name("terminatedVictimReader")
.dataSource(dataSource)
.sql("SELECT * FROM victims WHERE status = ? AND terminated_at <= ?") .queryArguments(List.of("TERMINATED", LocalDateTime.now())) .beanRowMapper(Victim.class) .build();
}
근데 내가 지금 하려고 하는 건 jdbc 커서 방식에서 일대다 관계 테이블 데이터를 조회해서 일일정산 데이터 만드는 기능으로 복습해보려고 하는데, 데이터가 뻥튀기 되서 강의 예제에서 해당 케이스를 찾아보려고 하는데, 못 찾아서 질문글 작성했어. jdbc 방식으로 일대다 관계 데이터를 Reader로 읽어와서 위에 jpa 구조로 매핑하려면 어떻게 해야하는지 알려줄 수 있어?
@Bean
@StepScope
public JdbcCursorItemReader<OrderBatchJoinDto> jdbcCursorReader( @Value("#{jobParameters['orderDate']}") LocalDate orderDate) {
LocalDateTime startOrderDate = orderDate.atStartOfDay();
LocalDateTime endOrderDate = orderDate.atTime(23, 59, 59);
return new JdbcCursorItemReaderBuilder<OrderBatchJoinDto>()
.name("jdbcCursorReader")
.dataSource(dataSource)
.sql("""
SELECT
ob.ORDERS_BATCH_ID,
ob.USER_ID, ob.STATUS,
ob.ORDER_DATE_TIME,
oib.ORDERS_ITEM_BATCH_ID,
oib.ORDERS_BATCH_ID,
oib.PRODUCT_BATCH_ID,
oib.PRODUCT_NAME,
oib.PRICE,
oib.QUANTITY
FROM orders_batch ob
LEFT JOIN orders_item_batch oib
ON ob.ORDERS_BATCH_ID = oib.ORDERS_BATCH_ID
WHERE ob.ORDER_DATE_TIME BETWEEN ? AND ?
ORDER BY ob.ORDERS_BATCH_ID, oib.ORDERS_ITEM_BATCH_ID
""")
.queryArguments(List.of(startOrderDate, endOrderDate))
.beanRowMapper(OrderBatchJoinDto.class)
}public class OrderItemBatchDto {
private Long id;
private Long ordersBatchId;
private Long productBatchId;
private String productName;
private int price;
private int quantity;
}public class OrderItemBatchDto {
private Long id;
private Long ordersBatchId;
private Long productBatchId;
private String productName;
private int price;
private int quantity;
}답변 2
1
반갑다 Mr형 어려운 질문이군 💀
기본적인 청크지향처리 구조(ETL)에서 자주 접하게되는 고민 지점이다
자, 대답은 조회한 데이터로 무엇을하려고하는가(어떤 처리과정을거쳐 무엇을 어떻게 쓰려고하는가)에 따라 달라질수 있다.
그러나 우선 일반론적인 대답부터 해보겠다. 추가적인 질문은 댓글로 부탁한다 💀💀
----
후보1 - ItemWriter단에서 집계
1) reader: LEFT JOIN 쿼리를 실행하고, 중복된 행 순차읽기
2) writer 내부에서 자료구조 사용해 데이터 임시 저장 /집계
-> 스텝이 끝나면 중복은 다 제거된 상태가 될 것이다. 그렇다면,
3) 후속 스텝에서 해당 객체 읽어서 쓰기
후보 2 - 쿼리 레벨에서 그룹화 및 집계
1) 데이터를 읽어올 때부터 중복이 발생하지 않도록 SQL 쿼리 조정
- 조인 결과가 중복되는 부분을 데이터베이스 레벨에서 GROUP BY
- 이것은 위 데이터를 조회해서 하고자하는 일에 따라 이야기가달라질수있다.
후보 3 - 위와 같이 간단한 케이스로해결안되는경우
step 1 - 메인 엔티티 id만읽어 메모리 / 파일 / 레디스 등 임시 저장소에 id 저장
step 2 - 해당 id를 읽어 processor에서 단에서 조회 또는 커스텀 itemReader로 Reader단에서 조회 등
여기서 부턴 구현하기 나름이다 💀💀💀0
안녕하세요, 인프런 AI 인턴입니다.
MrLee님, JDBC 방식을 사용하여 일대다 관계 데이터를 처리하려고 할 때 발생하는 이슈에 대해 질문 주셨네요. 보통 JPA로는 페치 조인을 사용하여 쉽게 일대다 관계를 처리할 수 있지만, JDBC에서는 별도의 접근이 필요합니다.
JDBC에서 일대다 관계 데이터 처리 방법
JDBC 커서를 통해 일대다 관계 데이터를 가져올 때, 일반적으로 우리가 원하는 것은 각각의 일대의 데이터가 여러 개의 다에 해당하는 데이터를 가질 수 있는 구조입니다. 하지만 JDBC는 단순한 ResultSet으로 데이터를 처리하기 때문에 각 행마다 일대다 관계에서 중복된 정보가 포함될 수 있습니다. 이를 해결하기 위해서는 비즈니스 로직에서 결과를 수동으로 집계 또는 매핑해야 합니다.
결과 수동 매핑: 각 ‘일’에 해당하는 엔티티를 처리하면서, 관련된 ‘다’의 데이터를 직접 수집하여 매핑하는 로직을 구현할 수 있습니다.
Map 구조를 사용한 데이터 처리:
Map<Long, OrderBatchJoinDto>와 같이 ID를 키로 사용하는 Map을 공간에 저장하여 각 결과를 처리하면서 데이터를 집계합니다.
@Bean
@StepScope
public ItemReader<OrderBatchJoinDto> jdbcCursorItemReader(
@Value("#{jobParameters['orderDate']}") LocalDate orderDate) {
LocalDateTime startOrderDate = orderDate.atStartOfDay();
LocalDateTime endOrderDate = orderDate.atTime(23, 59, 59);
return new JdbcCursorItemReaderBuilder<OrderBatchJoinDto>()
.name("jdbcCursorReader")
.dataSource(dataSource)
.sql("""
SELECT
ob.ORDERS_BATCH_ID,
ob.USER_ID,
ob.STATUS,
ob.ORDER_DATE_TIME,
oib.ORDERS_ITEM_BATCH_ID,
oib.ORDERS_BATCH_ID,
oib.PRODUCT_BATCH_ID,
oib.PRODUCT_NAME,
oib.PRICE,
oib.QUANTITY
FROM orders_batch ob
LEFT JOIN orders_item_batch oib
ON ob.ORDERS_BATCH_ID = oib.ORDERS_BATCH_ID
WHERE ob.ORDER_DATE_TIME BETWEEN ? AND ?
ORDER BY ob.ORDERS_BATCH_ID, oib.ORDERS_ITEM_BATCH_ID
""")
.queryArguments(List.of(startOrderDate, endOrderDate))
.rowMapper((resultSet, rowNum) -> {
// map and aggregate your OrderBatchJoinDto from the ResultSet
// logic to process each row and add to a map or collection
// for aggregation
OrderBatchJoinDto dto = new OrderBatchJoinDto();
// Set properties from resultSet to dto
// Add to aggregated structure
return dto;
})
.fetchSize(10)
.build();
}
위 방법은 가장 효과적으로 중복되어 조회되는 데이터를 집계하여 일대다 구조로 변환하는 방식입니다. 각 OrderBatchJoinDto 객체를 생성하고, 필요한 로직으로 ID별로 데이터를 관리하고 집계합니다.
지금 설명드린 방식에 대한 구체적인 사례는, 비슷한 토픽이 논의된 게시글 JDBC에서의 데이터 중복 처리에 관련된 경험를 참조하시면 도움이 될 것 같습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해 드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.





내가 연습으로 만들려고 했던 기능은 주문, 주문 항목의 데이터를 특정 날짜별로 요약해서 하루에 1 건의 일일 정산 데이터를 정산 테이블에 입력하는 예제를 만들어 보려고 했는데 생각보다 구현이 쉽지 않을 거 같아서 우선은 다른 주제로 연습하려고 해.
혹시 실제 실무에서는 jdbc 방식으로 위와 같은 요구사항을 배치로 처리한다면, 후보1, 후보2, 후보3 중에서 어떤 방법을 사용하는지도 알려줄 수 있어?
그리고 추가로 위처럼 실제 실무에서 만날 수 있는 상황의 예제 코드로 새로운 강의를 만들 생각은 없는 지 궁금해.
현재 강의에서는 복잡하지 않은 기능에 대한 코드는 많은데, 진짜 실제 실무에서 요구사항에 따른 구현 코드를 어떻게 작성하는 지 알려주는 강의가 있으면 좋을 거 같아서 추가 의견도 남겨봤어.