2025. 5. 6. 19:23ㆍSpring Boot
💥 Spring Batch에서 Entity Not Mapped 오류 발생
Spring Batch에서 여러 데이터소스를 설정하고,
그 중 하나(subEntityManagerFactory)를 이용해서 JpaPagingItemReader로 조회하려고 했다.
하지만 Batch 실행 중 다음 에러가 발생했다.
org.hibernate.hql.internal.ast.QuerySyntaxException: EntityA is not mapped
처음엔 Entity 매핑 문제를 의심했지만, 엔티티도 제대로 선언되어 있었고, Config도 문제없었다.
결국 원인은 EntityManagerFactory 주입 방식 문제였다.
🔍 Bean 주입 시점과 프록시로 발생한 문제 분석
Batch는 기본적으로 ApplicationContext 기동 시점에 모든 빈을 싱글톤으로 생성한다.
그런데 @StepScope를 사용하면 Step 실행 시점에 빈이 생성된다.
이때 필드에 주입되어 있던 EntityManagerFactory는 실제 객체가 아니라 프록시(Proxy) 객체다.
문제는 이 프록시가 내부적으로 기본 EntityManagerFactory를 바라볼 수도 있다는 점.
@StepScope
public JpaPagingItemReader<EntityA> entityReader() {
...
.entityManagerFactory(entityManagerFactory) // ❌ 프록시 주입 → 실제 대상이 기본 DB일 수 있음
}
결과적으로 JpaPagingItemReader는 기본 DB를 바라보게 되고, EntityA를 찾지 못해 에러가 발생한다.
(Entity A는 기본 DB가 아닌 멀티데이터소스 설정으로 인해 다른 DB에 존재하고 있으니!)
진짜 핵심은 StepScope의 생성 타이밍 때문에, 필드 주입은 신뢰할 수 없다는 것.
📘 StepScope란? Spring Batch에서의 Bean 생명주기 설명
Step 실행 시점에 Bean을 생성한다.
Step마다 새로운 객체가 만들어진다.
주로 ItemReader, ItemProcessor, ItemWriter 같은 Step 내부 컴포넌트에 붙인다.
StepExecutionContext 범위 안에서 관리된다.
✅ 사용하는 이유
매 Step 실행마다 다른 데이터를 주입하고 싶을 때 (예: JobParameter, 현재 날짜 등)
Step 실행 전에는 Bean을 생성하지 않고, Step 실행 시점에 생성하기 위해
✅ Bean 생성 타이밍
애플리케이션 기동 시점 → Proxy 객체만 등록
Step 실행 시점 → 실제 Bean 생성
기존 코드
@Slf4j
@Configuration
@RequiredArgsConstructor
public class SampleBatchJobConfig {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Qualifier("subEntityManagerFactory")
private final EntityManagerFactory entityManagerFactory;
@Bean
@StepScope
public JpaPagingItemReader<EntityA> entityReader() {
Map<String, Object> params = new HashMap<>();
params.put("targetDate", LocalDate.now());
params.put("status", StatusType.ACTIVE);
return new JpaPagingItemReaderBuilder<EntityA>()
.name("entityReader")
.entityManagerFactory(entityManagerFactory) // 문제 발생 지점
.pageSize(100)
.queryString("""
SELECT e
FROM EntityA e
WHERE e.expireDate < :targetDate
AND e.status = :status
""")
.parameterValues(params)
.build();
}
}
개선된 코드
@Slf4j
@Configuration
@RequiredArgsConstructor
public class SampleBatchJobConfig {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
@StepScope
public JpaPagingItemReader<EntityA> entityReader(
@Qualifier("subEntityManagerFactory") EntityManagerFactory subEntityManagerFactory) {
Map<String, Object> params = new HashMap<>();
params.put("targetDate", LocalDate.now());
params.put("status", StatusType.ACTIVE);
return new JpaPagingItemReaderBuilder<EntityA>()
.name("entityReader")
// ✅ 메소드 파라미터 주입
.entityManagerFactory(subEntityManagerFactory)
.pageSize(100)
.queryString("""
SELECT e
FROM EntityA e
WHERE e.expireDate < :targetDate
AND e.status = :status
""")
.parameterValues(params)
.build();
}
}
이렇게 수정된 코드에서는 @StepScope 빈에서 필드 주입 대신 메서드 파라미터 주입을 사용하여, EntityManagerFactory를 정확하게 주입할 수 있게 처리했다.
이를 통해 프록시 객체가 아닌 실제 subEntityManagerFactory가 주입되도록 설정한다.
🔧 StepScope에서 메서드 주입을 통한 문제 해결
- @StepScope 빈에서는 필드 주입 대신 메소드 파라미터 주입을 사용한다.
- @Qualifier를 메소드 파라미터에 붙여서 원하는 EntityManagerFactory를 정확히 지정한다.
- 프록시가 개입하지 않고, 실제 객체가 정확히 주입된다.
📊 StepScope와 JobScope의 차이점 및 사용 시점
구분 | StepScope | JobScope |
생성 시점 | Step 실행 시점 | Job 시작 시점 |
스코프 범위 | Step마다 새로운 객체 생성 | Job마다 하나의 객체 생성 |
주 사용처 | Reader, Processor, Writer 같은 Step 단위 컴포넌트 | Job 전체 설정, 공통 값 공유할 때 |
예시 | 매 Step마다 날짜, 상태값 다르게 쓰고 싶을 때 | 전체 Job에서 파라미터를 공유할 때 |
한마디로 정리하면
- @StepScope : Step이 실행될 때마다 다른 값을 쓸 수 있게 해주는 스코프
- @JobScope : Job 단위로 한 번만 초기화되고 여러 Step이 공유하는 스코프
최종 정리
- @StepScope가 붙은 Bean에서는 무조건 메소드 파라미터 주입을 쓰자.
- 특히 다중 데이터소스 환경에서는, 어떤 EntityManagerFactory가 주입되는지 명확히 해야 한다.
- 이걸 놓치면 Hibernate가 기본 DB를 바라보다가 원하는 Entity를 찾지 못해서 터진다.
느낀 점
처음엔 단순한 엔티티 매핑 문제라고 생각했다. (이 것 때문에 진짜 오만가지 설정을 다시 해보고 난리였음..ㅠ)
근데 진짜 원인은 Bean의 생성 시점 차이였고, 그걸 이해하니까 모든 게 풀렸다.
Batch + 다중 데이터소스 + StepScope 조합은 StepScope의 동작 방식을 이해하고 잘 써야 한다.
이제부터는 @StepScope 쓰면 무조건 파라미터 주입을 사용하는 습관을 들이자.
'Spring Boot' 카테고리의 다른 글
OOM 원인 분석기, 근데 이제 데드락과 스레드 (0) | 2025.05.12 |
---|---|
Spring Security @EnabledWebSecurity 의 동작원리 (0) | 2024.06.17 |
[Spring Boot] 이메일 보내기 (3) - html 템플릿 적용 (feat. Thymeleaf) (0) | 2022.08.11 |
[Spring Boot] Thymeleaf 경로 변경 & 다중 경로 설정 (0) | 2022.08.11 |
[Spring Boot] 이메일 보내기 (2) - 참조(cc), 첨부 파일 (8) | 2022.08.10 |