이 Spring Batch 프로젝트는 CSV 파일 처리와 실시간 날씨 데이터 수집 두 가지 배치 시스템을 포함합니다.
springBatch/
├── src/main/java/com/springbatch/
│ ├── SpringBatchApplication.java # 메인 애플리케이션
│ ├── config/ # 설정 클래스들
│ │ ├── BatchConfig.java # CSV 배치 설정
│ │ └── WeatherBatchConfig.java # 날씨 배치 설정
│ ├── controller/ # 웹 컨트롤러
│ │ ├── BatchController.java # 배치 실행 컨트롤러
│ │ └── WeatherController.java # 날씨 API 컨트롤러
│ ├── entity/ # JPA 엔티티
│ │ ├── Person.java # 사용자 엔티티
│ │ └── WeatherData.java # 날씨 데이터 엔티티
│ ├── dto/ # 데이터 전송 객체
│ │ └── WeatherApiResponse.java # 날씨 API 응답 DTO
│ ├── repository/ # 데이터 접근 계층
│ │ ├── PersonRepository.java # 사용자 데이터 리포지토리
│ │ └── WeatherDataRepository.java # 날씨 데이터 리포지토리
│ └── service/ # 비즈니스 로직
│ └── WeatherApiService.java # 날씨 API 서비스
├── src/main/resources/
│ ├── templates/ # Thymeleaf 템플릿
│ │ ├── index.html # 메인 페이지
│ │ └── weather-dashboard.html # 날씨 대시보드
│ ├── application.properties # 애플리케이션 설정
│ └── sample-data.csv # 샘플 CSV 데이터
├── .env # 환경 변수 (API 키)
└── build.gradle # Gradle 빌드 설정
@Configuration
public class BatchConfig {
// Job: 전체 배치 작업 정의
@Bean
public Job importUserJob(Step step1) {
return new JobBuilder("importUserJob", jobRepository)
.start(step1)
.build();
}
// Step: 실제 처리 단계
@Bean
public Step step1(ItemReader<Person> reader,
ItemProcessor<Person, Person> processor,
ItemWriter<Person> writer) {
return new StepBuilder("step1", jobRepository)
.<Person, Person>chunk(3, transactionManager)
.reader(reader) // CSV 파일 읽기
.processor(processor) // 데이터 변환/검증
.writer(writer) // 데이터베이스 저장
.build();
}
}
처리 흐름:
1. ItemReader: sample-data.csv 파일에서 사용자 데이터 읽기
2. ItemProcessor: 데이터 검증 및 변환 (이메일 형식 체크 등)
3. ItemWriter: H2 데이터베이스에 Person 엔티티로 저장
@Configuration
public class WeatherBatchConfig {
// 날씨 데이터 수집 Job
@Bean
public Job collectWeatherDataJob(Step weatherCollectionStep) {
return new JobBuilder("collectWeatherDataJob", jobRepository)
.start(weatherCollectionStep)
.build();
}
// 날씨 데이터 처리 Step
@Bean
public Step weatherCollectionStep(ItemReader<String> cityReader,
ItemProcessor<String, WeatherData> weatherProcessor,
ItemWriter<WeatherData> weatherWriter) {
return new StepBuilder("weatherCollectionStep", jobRepository)
.<String, WeatherData>chunk(3, transactionManager)
.reader(cityReader) // 도시 목록 생성
.processor(weatherProcessor) // API 호출 및 데이터 변환
.writer(weatherWriter) // 데이터베이스 저장
.build();
}
}
처리 흐름:
1. ItemReader: 전국 주요 8개 도시 목록 생성 (Seoul, Busan, Incheon 등)
2. ItemProcessor: 각 도시별 OpenWeatherMap API 호출 → WeatherData 엔티티 변환
3. ItemWriter: H2 데이터베이스에 날씨 데이터 저장
4. 이상 기후 탐지: 전날 대비 온도 변화량 계산 (20도 이상 차이 시 이상 기후로 판정)
@Entity
@Table(name = "person")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
private String email;
}
@Entity
@Table(name = "weather_data")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WeatherData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String cityCode; // 도시 코드
@Column(nullable = false)
private String cityName; // 도시명 (한국어)
@Column(nullable = false)
private Double temperature; // 현재 온도
private Double feelsLike; // 체감 온도
private Double tempMin; // 최저 온도
private Double tempMax; // 최고 온도
private Integer humidity; // 습도
private Integer pressure; // 기압
private Double windSpeed; // 풍속
private Integer windDirection; // 풍향
private Integer cloudiness; // 구름량
private Double rainfall; // 강수량 (1시간)
private Double snowfall; // 적설량 (1시간)
private Integer visibility; // 가시거리
private String weatherMain; // 날씨 상태 (Rain, Clear 등)
private String weatherDescription; // 날씨 설명
@Column(nullable = false)
private LocalDateTime collectedAt; // 데이터 수집 시간
private LocalDateTime weatherTime; // 날씨 관측 시간
// 이상 기후 탐지 필드
private Boolean isAbnormal = false; // 이상 기후 여부
private Double temperatureChange; // 전날 대비 온도 변화량
}
사용자 요청 → BatchController.runJob()
→ JobLauncher.run(importUserJob)
→ Step1 실행
→ CSV 파일 읽기 → 데이터 처리 → DB 저장
사용자 요청 → WeatherController.collectWeatherData()
→ JobLauncher.run(collectWeatherDataJob)
→ WeatherCollectionStep 실행
→ 도시 목록 생성 → API 호출 → 데이터 변환 → 이상기후 탐지 → DB 저장
private void detectAbnormalWeather(WeatherData currentData) {
// 전날 동일 시간대 데이터 조회
var yesterdayDataList = weatherDataRepository
.findByCityCodeAndCollectedAtBetweenOrderByCollectedAtDesc(
currentData.getCityCode(), yesterdayStart, yesterdayEnd);
if (!yesterdayDataList.isEmpty()) {
WeatherData yesterdayData = yesterdayDataList.get(0);
double temperatureChange = currentData.getTemperature() - yesterdayData.getTemperature();
// 전날 대비 20도 이상 변화 시 이상 기후로 판단
if (Math.abs(temperatureChange) >= 20.0) {
currentData.setIsAbnormal(true);
}
}
}
WEATHER_API_KEY=4336a7df2186cc57454035002475e06a
# H2 Database
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
# JPA
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# Batch
spring.batch.job.enabled=false
이 구조를 기반으로 다양한 배치 시스템을 확장할 수 있으며, 각 컴포넌트는 독립적으로 테스트 및 운영 가능합니다.