멀티 DB 커넥션을 사용하는 대용량 배치 샘플 프로젝트입니다.
billing + billing_detail + customer 데이터를 조합해 bill_data 테이블에 NDJSON 라인을 적재합니다.
- 2개 업무 DB(
bill,customer) + 1개 배치 메타 DB(batch) 분리 billing대상 건 조회 후 상세/고객 정보를 조합- 결과를
bill_data.payload_ndjson에 NDJSON 포맷으로 저장 - 대량(약 1,000만 건 이상) 처리를 고려한 Keyset 기반 Chunk/Page 배치 처리
- Java 25
- Spring Boot 4.0.5
- Spring Batch 6.x
- MyBatis (mybatis-spring-boot-starter 4.0.1)
- H2
- Lombok
- Gradle Wrapper
- bill 스키마
billing(메인)billing_detail(서브)bill_data(결과 저장)- customer 스키마
customer(billing.id와 1:1 연관)
관계:
billing:billing_detail= 1:1billing:customer= 1:1
spring-batch-gradle-sample
├─ src/main/java/io/github/libedi/demo
│ ├─ DemoApplication.java
│ └─ batch
│ ├─ config
│ │ ├─ AppBatchProperties.java
│ │ ├─ DataSourceConfiguration.java
│ │ └─ MybatisConfiguration.java
│ ├─ domain
│ │ ├─ BillingHeader.java
│ │ ├─ BillingDetail.java
│ │ ├─ CustomerInfo.java
│ │ └─ BillDataLine.java
│ ├─ mapper
│ │ ├─ bill
│ │ │ ├─ BillingMapper.java
│ │ │ └─ BillDataMapper.java
│ │ └─ customer
│ │ └─ CustomerMapper.java
│ └─ job
│ ├─ BillingKeysetChunkReader.java
│ ├─ BillingJoinChunkProcessor.java
│ ├─ BillingNdjsonItemWriter.java
│ └─ BillingNdjsonJobConfiguration.java
│ └─ subtable
│ ├─ SubTableReader.java
│ ├─ SubTableRecord.java
│ ├─ BillingDetailSubTableReader.java
│ └─ CustomerSubTableReader.java
├─ src/main/resources
│ ├─ application.yml
│ ├─ config/application.yml
│ └─ schema
│ ├─ bill.sql
│ └─ customer.sql
├─ src/test
│ ├─ java/io/github/libedi/demo/batch/BillingNdjsonJobIntegrationTest.java
│ └─ resources
│ ├─ application-test.yml
│ └─ schema
│ ├─ bill.sql
│ └─ customer.sql
└─ AGENTS.md
batchDataSource(Primary): JobRepository/배치 메타데이터 전용billDataSource:billing,billing_detail,bill_data읽기/쓰기customerDataSource:customer읽기 전용- 각 데이터소스별
PlatformTransactionManager를 별도 구성해 트랜잭션 경계를 명확히 분리
- bill/customer 각각 독립
SqlSessionFactory,SqlSessionTemplate구성 @MapperScan을 패키지 기준으로 분리해 매퍼가 올바른 DB 커넥션을 사용하도록 강제
- Reader:
BillingKeysetChunkReader - Keyset 조건(
id > lastId and id <= maxId) +ORDER BY id ASC+LIMIT로 대상 ID 페이지를 조회 - Reader는
ExecutionContext에lastId를 저장/복원해 재시작을 지원 - Processor:
BillingJoinChunkProcessor - 청크 단위 ID 목록을 받아
billing_header + billing_detail + customer를 배치 조회/조합 - 서브테이블 조회는
SubTableReader구현체(BillingDetailSubTableReader,CustomerSubTableReader)로 분리 - Writer:
BillingNdjsonItemWriter - Processor 결과를 저장(
bill_datainsert)하고 처리 완료 상태(billing.processed = TRUE)를 갱신
- 한 레코드당 JSON 라인 1개를 생성하고 줄바꿈(
\n)으로 종료 - 필드 순서를 고정해(LinkedHashMap 기반) 테스트/검증 시 일관된 결과 확보
@SqlGroup+ datasource별@SqlConfig로 bill/customer 테스트 데이터를 분리 주입- Step 슬라이스 테스트: 실제
billingNdjsonStep을 단일 Step Job(billingNdjsonStepSliceJob)으로 감싸 검증 - Job 통합 테스트: 실제
billingNdjsonJob전체 실행 후 결과 데이터/상태 검증 - 실행 진입점은
JobOperator.start(Job, JobParameters)사용 (deprecated 방식 제외)
billing에서 Keyset 방식으로 미처리 대상 ID 페이지 조회- Processor가 청크 단위로
billing_detail,customer를 배치 조회해 조합 - 조합 결과를 NDJSON 라인으로 생성
- Writer가
bill_data에 배치 insert - Writer가 처리된
billing건을processed = true로 업데이트
- 미사용 클래스 제거:
BillingIdPagingReader,BillingLineItemProcessor - 기존 offset/단건 조합 관점을 정리하고 Keyset 기반 Reader-Processor-Writer 책임 분리를 강화
- 서브테이블 조회 확장 지점으로
SubTableReader인터페이스를 도입해 OCP 친화 구조로 개선
주요 설정 파일:
src/main/resources/application.ymlsrc/main/resources/config/application.yml
원칙:
- 설정은
application.yml중심 - Java Config는 필요한 경우에만 사용
- deprecated API 사용 금지
프로젝트 루트에서 실행:
./gradlew clean build./gradlew testWindows PowerShell:
.\gradlew clean build
.\gradlew test- Step 슬라이스 테스트 + Job 통합 테스트를 모두 유지
- 테스트 데이터 준비는
@Sql기반 - 테스트용 DDL/DML 위치:
src/test/resources/schema/bill.sql,src/test/resources/schema/customer.sql
예시 통합 테스트:
src/test/java/io/github/libedi/demo/batch/BillingNdjsonJobIntegrationTest.java
- TDD 기반으로 기능 개발
- 테스트 통과 코드만 커밋
- 동일 테스트 5회 연속 실패 시 원인/대안 정리 후 재진행
- 중복 코드는 리팩토링으로 공통화
- 기본 H2 기반 샘플 구성입니다.
- 실제 운영에서는 DB 연결 정보/스키마/성능 파라미터를 환경별로 분리하세요.