diff --git a/README_BATCH_SEQUENCE_FIX.md b/README_BATCH_SEQUENCE_FIX.md new file mode 100644 index 00000000..53f36e8a --- /dev/null +++ b/README_BATCH_SEQUENCE_FIX.md @@ -0,0 +1,108 @@ +# Spring Batch Sequence 문제 해결 가이드 + +## 문제 상황 + +SQL Server의 예전 버전(2012 이전)에서는 sequence를 지원하지 않아 Spring Batch 실행 시 오류가 발생할 수 있습니다. + +## 해결 방법 + +### 1. BatchConfiguration 클래스 생성 + +`src/main/java/com/interplug/qcast/config/batch/BatchConfiguration.java` 파일이 생성되었습니다. +이 클래스는 JobRepository를 커스터마이징하여 SQL Server에 맞는 설정을 적용합니다. + +### 2. application.yml 설정 수정 + +`src/main/resources/config/application-local.yml` 파일의 batch 설정이 수정되었습니다: + +```yaml +spring: + batch: + jdbc: + initialize-schema: never + table-prefix: BATCH_ + schema: # 스키마가 필요한 경우 설정 +``` + +### 3. 데이터베이스 테이블 생성 + +`src/main/resources/db/migration/create_batch_id_tables.sql` 스크립트를 실행하여: + +- Spring Batch 메타데이터 테이블 생성 +- sequence 대신 사용할 ID 생성 테이블 생성 + +## 적용 순서 + +1. **데이터베이스 스크립트 실행** + + ```sql + -- create_batch_id_tables.sql 파일의 내용을 데이터베이스에서 실행 + ``` + +2. **애플리케이션 재시작** + + - 새로운 BatchConfiguration이 적용됩니다. + +3. **테스트** + - 간단한 배치 작업을 실행하여 정상 동작 확인 + +## 주요 변경사항 + +### BatchConfiguration.java + +- JobRepository를 커스터마이징 +- SQL Server 데이터베이스 타입 명시적 설정 +- 테이블 기반 ID 생성 사용 + +### SQL 스크립트 + +- `BATCH_JOB_SEQ`, `BATCH_JOB_EXECUTION_SEQ`, `BATCH_STEP_EXECUTION_SEQ` 테이블 생성 +- Spring Batch 메타데이터 테이블들 생성 (없는 경우) + +## 추가 설정 옵션 + +필요에 따라 다음 설정들을 조정할 수 있습니다: + +### application.yml + +```yaml +spring: + batch: + jdbc: + initialize-schema: always # 자동으로 테이블 생성 (개발환경) + table-prefix: CUSTOM_ # 테이블 prefix 변경 + schema: dbo # 스키마 명시적 설정 +``` + +### BatchConfiguration.java + +```java +// 격리 레벨 변경 +factory.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED"); + +// 커스텀 테이블 prefix +factory.setTablePrefix("CUSTOM_BATCH_"); +``` + +## 문제 해결 + +### 여전히 sequence 오류가 발생하는 경우: + +1. 데이터베이스에 sequence 관련 객체가 남아있는지 확인 +2. Spring Batch 메타데이터 테이블을 모두 삭제 후 재생성 +3. 애플리케이션 캐시 클리어 후 재시작 + +### 권한 문제: + +SQL Server에서 테이블 생성 및 ID 생성 테이블 접근 권한이 필요합니다. + +```sql +-- 필요한 권한 확인 +GRANT SELECT, INSERT, UPDATE, DELETE ON BATCH_* TO [username]; +``` + +## 참고사항 + +- 이 설정은 SQL Server 2008, 2008 R2, 2012 이전 버전에서 사용 +- SQL Server 2012 이후 버전에서도 호환성을 위해 사용 가능 +- 운영환경에서는 `initialize-schema: never`로 설정 권장 diff --git a/src/main/java/com/interplug/qcast/config/batch/BatchConfiguration.java b/src/main/java/com/interplug/qcast/config/batch/BatchConfiguration.java new file mode 100644 index 00000000..ed11173b --- /dev/null +++ b/src/main/java/com/interplug/qcast/config/batch/BatchConfiguration.java @@ -0,0 +1,57 @@ +package com.interplug.qcast.config.batch; + +import javax.sql.DataSource; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.support.DatabaseType; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.task.TaskExecutor; +import org.springframework.jdbc.support.incrementer.SqlServerMaxValueIncrementer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Spring Batch Configuration for SQL Server without sequence support + */ +@Configuration +public class BatchConfiguration { + + /** + * Custom JobRepository that uses table-based ID generation instead of sequences + */ + @Bean + @Primary + public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager) throws Exception { + JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + factory.setDataSource(dataSource); + factory.setTransactionManager(transactionManager); + + // 테이블 prefix 설정 + factory.setTablePrefix("BATCH_"); + + // 격리 레벨 설정 + factory.setIsolationLevelForCreate("ISOLATION_SERIALIZABLE"); + + // SQL Server 데이터베이스 타입 설정 + factory.setDatabaseType(DatabaseType.SQLSERVER.getProductName()); + + factory.afterPropertiesSet(); + return factory.getObject(); + } + + /** + * TaskExecutor for async batch processing + */ + @Bean + public TaskExecutor batchTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(25); + executor.setThreadNamePrefix("batch-"); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/src/main/resources/config/application-local.yml b/src/main/resources/config/application-local.yml index b165146a..0ade68b5 100644 --- a/src/main/resources/config/application-local.yml +++ b/src/main/resources/config/application-local.yml @@ -35,6 +35,9 @@ spring: batch: jdbc: initialize-schema: never + # SQL Server 예전 버전에서 sequence 대신 table 기반 ID 생성 사용 + table-prefix: BATCH_ + schema: # 스키마가 필요한 경우 설정 job: names: ${job.name:NONE} enabled: false @@ -68,4 +71,4 @@ file: ini.drawing.img.path: https://files.hanasys.jp/Drawing front: - url: http://localhost:8080 \ No newline at end of file + url: http://localhost:8080 diff --git a/src/main/resources/db/migration/create_batch_id_tables.sql b/src/main/resources/db/migration/create_batch_id_tables.sql new file mode 100644 index 00000000..cc2e9545 --- /dev/null +++ b/src/main/resources/db/migration/create_batch_id_tables.sql @@ -0,0 +1,125 @@ +-- Spring Batch용 ID 생성 테이블 생성 (SQL Server 예전 버전용) +-- sequence 대신 테이블 기반 ID 생성을 사용 + +-- Job Instance ID 생성 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_JOB_SEQ]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_JOB_SEQ ( + ID BIGINT NOT NULL + ); + INSERT INTO BATCH_JOB_SEQ VALUES (0); +END + +-- Job Execution ID 생성 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_JOB_EXECUTION_SEQ]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_JOB_EXECUTION_SEQ ( + ID BIGINT NOT NULL + ); + INSERT INTO BATCH_JOB_EXECUTION_SEQ VALUES (0); +END + +-- Step Execution ID 생성 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_STEP_EXECUTION_SEQ]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_STEP_EXECUTION_SEQ ( + ID BIGINT NOT NULL + ); + INSERT INTO BATCH_STEP_EXECUTION_SEQ VALUES (0); +END + +-- 기존 Spring Batch 메타데이터 테이블들이 없는 경우 생성 +-- Job Instance 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_JOB_INSTANCE]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_JOB_INSTANCE ( + JOB_INSTANCE_ID BIGINT IDENTITY PRIMARY KEY, + VERSION BIGINT, + JOB_NAME VARCHAR(100) NOT NULL, + JOB_KEY VARCHAR(32) NOT NULL, + constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) + ); +END + +-- Job Execution 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_JOB_EXECUTION]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_JOB_EXECUTION ( + JOB_EXECUTION_ID BIGINT IDENTITY PRIMARY KEY, + VERSION BIGINT, + JOB_INSTANCE_ID BIGINT NOT NULL, + CREATE_TIME DATETIME2 NOT NULL, + START_TIME DATETIME2 DEFAULT NULL, + END_TIME DATETIME2 DEFAULT NULL, + STATUS VARCHAR(10), + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), + LAST_UPDATED DATETIME2, + JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL, + constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID) references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID) + ); +END + +-- Job Execution Parameters 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_JOB_EXECUTION_PARAMS]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_JOB_EXECUTION_PARAMS ( + JOB_EXECUTION_ID BIGINT NOT NULL, + TYPE_CD VARCHAR(6) NOT NULL, + KEY_NAME VARCHAR(100) NOT NULL, + STRING_VAL VARCHAR(250), + DATE_VAL DATETIME2 DEFAULT NULL, + LONG_VAL BIGINT, + DOUBLE_VAL DOUBLE PRECISION, + IDENTIFYING CHAR(1) NOT NULL, + constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) + ); +END + +-- Step Execution 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_STEP_EXECUTION]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_STEP_EXECUTION ( + STEP_EXECUTION_ID BIGINT IDENTITY PRIMARY KEY, + VERSION BIGINT NOT NULL, + STEP_NAME VARCHAR(100) NOT NULL, + JOB_EXECUTION_ID BIGINT NOT NULL, + START_TIME DATETIME2 NOT NULL, + END_TIME DATETIME2 DEFAULT NULL, + STATUS VARCHAR(10), + COMMIT_COUNT BIGINT, + READ_COUNT BIGINT, + FILTER_COUNT BIGINT, + WRITE_COUNT BIGINT, + READ_SKIP_COUNT BIGINT, + WRITE_SKIP_COUNT BIGINT, + PROCESS_SKIP_COUNT BIGINT, + ROLLBACK_COUNT BIGINT, + EXIT_CODE VARCHAR(2500), + EXIT_MESSAGE VARCHAR(2500), + LAST_UPDATED DATETIME2, + constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) + ); +END + +-- Step Execution Context 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_STEP_EXECUTION_CONTEXT]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT ( + STEP_EXECUTION_ID BIGINT PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT, + constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID) references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID) + ); +END + +-- Job Execution Context 테이블 +IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[BATCH_JOB_EXECUTION_CONTEXT]') AND type in (N'U')) +BEGIN + CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT ( + JOB_EXECUTION_ID BIGINT PRIMARY KEY, + SHORT_CONTEXT VARCHAR(2500) NOT NULL, + SERIALIZED_CONTEXT TEXT, + constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID) references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID) + ); +END \ No newline at end of file