From c2ea331b20e3da9edb7e9f79e646dccceb811e00 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Mon, 16 Mar 2026 17:49:50 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC):?= =?UTF-8?q?=20Quartz=20JDBC=20Store=20=EB=AF=B8=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=E2=80=94=20RAMJobStore=EC=97=90=EC=84=9C?= =?UTF-8?q?=20JDBC=20Store=EB=A1=9C=20=EC=A0=84=ED=99=98=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuartzConfig에 DataSource/QuartzProperties 명시적 주입 (커스텀 빈이 auto-config을 비활성화하는 문제) - initialize-schema를 never로 변경 (수동 DDL 관리) - std_snp_data 스키마용 Quartz DDL 스크립트 추가 Co-Authored-By: Claude Opus 4.6 --- .../snp/batch/global/config/QuartzConfig.java | 25 ++- src/main/resources/application-dev.yml | 2 +- src/main/resources/application.yml | 2 +- .../resources/sql/quartz_tables_postgres.sql | 187 ++++++++++++++++++ 4 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/sql/quartz_tables_postgres.sql diff --git a/src/main/java/com/snp/batch/global/config/QuartzConfig.java b/src/main/java/com/snp/batch/global/config/QuartzConfig.java index 15948e2..ef35824 100644 --- a/src/main/java/com/snp/batch/global/config/QuartzConfig.java +++ b/src/main/java/com/snp/batch/global/config/QuartzConfig.java @@ -11,28 +11,43 @@ import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.scheduling.quartz.SpringBeanJobFactory; import javax.sql.DataSource; +import java.util.Properties; /** * Quartz 설정 - * Spring Boot Auto-configuration을 사용하면서 JobFactory만 커스터마이징 + * 커스텀 SchedulerFactoryBean을 정의하면 Spring Boot auto-configuration이 비활성화되므로 + * DataSource와 QuartzProperties를 명시적으로 주입해야 한다. */ @Configuration public class QuartzConfig { /** * Quartz Scheduler Factory Bean 설정 - * Spring Boot Auto-configuration이 DataSource를 자동 주입하므로 - * JobFactory만 커스터마이징 + * DataSource, QuartzProperties를 명시적으로 주입하여 JDBC Store 사용 보장 */ @Bean - public SchedulerFactoryBean schedulerFactoryBean(ApplicationContext applicationContext) { + public SchedulerFactoryBean schedulerFactoryBean( + ApplicationContext applicationContext, + DataSource dataSource, + QuartzProperties quartzProperties) { + SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setJobFactory(springBeanJobFactory(applicationContext)); + factory.setDataSource(dataSource); factory.setOverwriteExistingJobs(true); // SchedulerInitializer에서 직접 start() 호출하므로 자동 시작 비활성화 // 자동 시작 시 JDBC Store의 기존 trigger가 로드되어 중복 실행 발생 가능 factory.setAutoStartup(false); - // DataSource는 Spring Boot가 자동 주입 (application.yml의 spring.datasource 사용) + + // application.yml의 spring.quartz.properties 적용 + // jobStore.class와 driverDelegateClass는 setDataSource()가 내부적으로 설정하므로 제외 + Properties properties = new Properties(); + quartzProperties.getProperties().forEach((key, value) -> { + if (!key.contains("jobStore.class") && !key.contains("driverDelegateClass")) { + properties.put(key, value); + } + }); + factory.setQuartzProperties(properties); return factory; } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 4b7a934..6cc6adb 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -42,7 +42,7 @@ spring: quartz: job-store-type: jdbc # JDBC store for schedule persistence jdbc: - initialize-schema: always # Create Quartz tables if not exist + initialize-schema: never # Quartz tables manually created in std_snp_data schema properties: org.quartz.scheduler.instanceName: SNPBatchScheduler org.quartz.scheduler.instanceId: AUTO diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d9898af..c13c002 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -42,7 +42,7 @@ spring: quartz: job-store-type: jdbc # JDBC store for schedule persistence jdbc: - initialize-schema: always # Create Quartz tables if not exist + initialize-schema: never # Quartz tables manually created in std_snp_data schema properties: org.quartz.scheduler.instanceName: SNPBatchScheduler org.quartz.scheduler.instanceId: AUTO diff --git a/src/main/resources/sql/quartz_tables_postgres.sql b/src/main/resources/sql/quartz_tables_postgres.sql new file mode 100644 index 0000000..8ebe8f8 --- /dev/null +++ b/src/main/resources/sql/quartz_tables_postgres.sql @@ -0,0 +1,187 @@ +-- Quartz Scheduler JDBC Store DDL for PostgreSQL +-- Schema: std_snp_data +-- tablePrefix 설정: std_snp_data.QRTZ_ +-- +-- 사용법: psql -d -f quartz_tables_postgres.sql + +SET search_path TO std_snp_data; + +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +CREATE TABLE QRTZ_JOB_DETAILS +( + SCHED_NAME VARCHAR(120) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + JOB_CLASS_NAME VARCHAR(250) NOT NULL, + IS_DURABLE BOOL NOT NULL, + IS_NONCONCURRENT BOOL NOT NULL, + IS_UPDATE_DATA BOOL NOT NULL, + REQUESTS_RECOVERY BOOL NOT NULL, + JOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +CREATE TABLE QRTZ_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + JOB_NAME VARCHAR(200) NOT NULL, + JOB_GROUP VARCHAR(200) NOT NULL, + DESCRIPTION VARCHAR(250) NULL, + NEXT_FIRE_TIME BIGINT NULL, + PREV_FIRE_TIME BIGINT NULL, + PRIORITY INTEGER NULL, + TRIGGER_STATE VARCHAR(16) NOT NULL, + TRIGGER_TYPE VARCHAR(8) NOT NULL, + START_TIME BIGINT NOT NULL, + END_TIME BIGINT NULL, + CALENDAR_NAME VARCHAR(200) NULL, + MISFIRE_INSTR SMALLINT NULL, + JOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) + REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) +); + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + REPEAT_COUNT BIGINT NOT NULL, + REPEAT_INTERVAL BIGINT NOT NULL, + TIMES_TRIGGERED BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_CRON_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + CRON_EXPRESSION VARCHAR(120) NOT NULL, + TIME_ZONE_ID VARCHAR(80), + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13, 4) NULL, + DEC_PROP_2 NUMERIC(13, 4) NULL, + BOOL_PROP_1 BOOL NULL, + BOOL_PROP_2 BOOL NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_BLOB_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + BLOB_DATA BYTEA NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_CALENDARS +( + SCHED_NAME VARCHAR(120) NOT NULL, + CALENDAR_NAME VARCHAR(200) NOT NULL, + CALENDAR BYTEA NOT NULL, + PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) +); + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS +( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_FIRED_TRIGGERS +( + SCHED_NAME VARCHAR(120) NOT NULL, + ENTRY_ID VARCHAR(95) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + FIRED_TIME BIGINT NOT NULL, + SCHED_TIME BIGINT NOT NULL, + PRIORITY INTEGER NOT NULL, + STATE VARCHAR(16) NOT NULL, + JOB_NAME VARCHAR(200) NULL, + JOB_GROUP VARCHAR(200) NULL, + IS_NONCONCURRENT BOOL NULL, + REQUESTS_RECOVERY BOOL NULL, + PRIMARY KEY (SCHED_NAME, ENTRY_ID) +); + +CREATE TABLE QRTZ_SCHEDULER_STATE +( + SCHED_NAME VARCHAR(120) NOT NULL, + INSTANCE_NAME VARCHAR(200) NOT NULL, + LAST_CHECKIN_TIME BIGINT NOT NULL, + CHECKIN_INTERVAL BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) +); + +CREATE TABLE QRTZ_LOCKS +( + SCHED_NAME VARCHAR(120) NOT NULL, + LOCK_NAME VARCHAR(40) NOT NULL, + PRIMARY KEY (SCHED_NAME, LOCK_NAME) +); + +-- Indexes +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP); + +CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME); +CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); + +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME); +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP); -- 2.45.2 From 6a0e9fa4ef98075ef61e0439c7e12b80d9d74190 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Mon, 16 Mar 2026 17:52:08 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE-NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 1123d10..30a77fd 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -41,6 +41,7 @@ - 재수집 관리 및 이력 추가 (#4) - 재수집 중복 실행 문제 해결 (#9) - Quartz orphan trigger 제거 (#12) +- Quartz JDBC Store 미적용 수정 — RAMJobStore에서 JDBC Store로 전환 (#12) - 마지막 성공 일시 세팅 방법 수정 (#15) - 테스트용 IMO 목록 건수 제한 제거 (#32) - 타임라인 상세 화면 이동 오류 수정 및 실행 중 작업 상세 버튼 추가 (#34) -- 2.45.2