Compare commits
9 Commits
596b6d4d84
...
feat/modif
| Author | SHA1 | Date | |
|---|---|---|---|
| 126b48ad3c | |||
| 2a3fa7e895 | |||
| 731dbb4170 | |||
| e50295e929 | |||
| 84d66b57ea | |||
| 038a4fabc6 | |||
| 4836d09320 | |||
| 1ce49fb793 | |||
| dee09ad16a |
50
kamco-make-dataset-generation/Jenkinsfile
vendored
50
kamco-make-dataset-generation/Jenkinsfile
vendored
@@ -1,12 +1,22 @@
|
|||||||
pipeline {
|
pipeline {
|
||||||
agent any
|
agent any
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
string(name: 'SPRING_PROFILES_ACTIVE', defaultValue: 'prod', description: 'Spring Profile (dev/prod)')
|
||||||
|
string(name: 'BATCH_DATE', defaultValue: '', description: 'Batch Date (YYYY-MM-DD, empty = today)')
|
||||||
|
string(name: 'ADDITIONAL_PARAMS', defaultValue: '', description: 'Additional Parameters (e.g., limit=100)')
|
||||||
|
choice(name: 'ACTION', choices: ['RUN', 'VERIFY_ONLY'], description: 'Action to perform')
|
||||||
|
}
|
||||||
|
|
||||||
tools {
|
tools {
|
||||||
jdk 'jdk21'
|
jdk 'jdk21'
|
||||||
}
|
}
|
||||||
|
|
||||||
environment {
|
environment {
|
||||||
BRANCH = 'main'
|
BRANCH = 'main'
|
||||||
GIT_REPO = 'https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-cron.git'
|
GIT_REPO = 'https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-cron.git'
|
||||||
JAR_NAME = 'generator-dataset-for-training.jar'
|
JAR_NAME = 'generator-dataset-for-training.jar'
|
||||||
|
TODAY = sh(script: "date +%Y-%m-%d", returnStdout: true).trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Pre-built JAR is included in the repository
|
// NOTE: Pre-built JAR is included in the repository
|
||||||
@@ -56,13 +66,49 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Deploy') {
|
stage('Run JAR') {
|
||||||
|
when {
|
||||||
|
expression { params.ACTION == 'RUN' }
|
||||||
|
}
|
||||||
steps {
|
steps {
|
||||||
dir("kamco-make-dataset-generation") {
|
dir("kamco-make-dataset-generation") {
|
||||||
script {
|
script {
|
||||||
def jarPath = "build/libs/${env.JAR_NAME}"
|
def jarPath = "build/libs/${env.JAR_NAME}"
|
||||||
echo "Ready to deploy: ${jarPath}"
|
|
||||||
|
|
||||||
|
// Determine batch date: use parameter if provided, otherwise use today
|
||||||
|
def batchDate = params.BATCH_DATE ?: env.TODAY
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "Running JAR: ${jarPath}"
|
||||||
|
echo "Profile: ${params.SPRING_PROFILES_ACTIVE}"
|
||||||
|
echo "Batch Date: ${batchDate}"
|
||||||
|
echo "Additional Params: ${params.ADDITIONAL_PARAMS}"
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
// Build Java command
|
||||||
|
def javaCmd = "java -jar ${jarPath}"
|
||||||
|
|
||||||
|
// Add Spring profile
|
||||||
|
if (params.SPRING_PROFILES_ACTIVE) {
|
||||||
|
javaCmd += " --spring.profiles.active=${params.SPRING_PROFILES_ACTIVE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add batch date parameter
|
||||||
|
javaCmd += " date=${batchDate}"
|
||||||
|
|
||||||
|
// Add additional parameters
|
||||||
|
if (params.ADDITIONAL_PARAMS) {
|
||||||
|
javaCmd += " ${params.ADDITIONAL_PARAMS}"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Executing: ${javaCmd}"
|
||||||
|
|
||||||
|
// Execute JAR
|
||||||
|
sh """
|
||||||
|
${javaCmd}
|
||||||
|
"""
|
||||||
|
|
||||||
|
echo "JAR execution completed successfully"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
257
kamco-make-dataset-generation/claudedocs/DATABASE_SETUP.md
Normal file
257
kamco-make-dataset-generation/claudedocs/DATABASE_SETUP.md
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
# 데이터베이스 설정 가이드
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
이 애플리케이션은 PostgreSQL 데이터베이스를 사용하며, 다음 테이블이 필요합니다:
|
||||||
|
1. Spring Batch 메타데이터 테이블 (자동 생성)
|
||||||
|
2. batch_history 테이블 (수동 생성 필요)
|
||||||
|
|
||||||
|
## 필수 테이블
|
||||||
|
|
||||||
|
### 1. Spring Batch 메타데이터 테이블
|
||||||
|
Spring Batch가 자동으로 생성합니다:
|
||||||
|
- BATCH_JOB_INSTANCE
|
||||||
|
- BATCH_JOB_EXECUTION
|
||||||
|
- BATCH_JOB_EXECUTION_PARAMS
|
||||||
|
- BATCH_STEP_EXECUTION
|
||||||
|
- BATCH_STEP_EXECUTION_CONTEXT
|
||||||
|
- BATCH_JOB_EXECUTION_CONTEXT
|
||||||
|
|
||||||
|
**설정**:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
batch:
|
||||||
|
jdbc:
|
||||||
|
initialize-schema: always
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. batch_history 테이블 (커스텀)
|
||||||
|
배치 작업 실행 이력을 저장하는 커스텀 테이블입니다.
|
||||||
|
|
||||||
|
**용도**:
|
||||||
|
- 배치 작업 시작/종료 시간 기록
|
||||||
|
- 배치 실행 상태 추적 (STARTED/COMPLETED/FAILED)
|
||||||
|
- 비즈니스 ID별 배치 이력 관리
|
||||||
|
|
||||||
|
## 새 환경 데이터베이스 초기 설정
|
||||||
|
|
||||||
|
### Option 1: SQL 스크립트 실행 (권장)
|
||||||
|
|
||||||
|
**1. batch_history 테이블 생성**:
|
||||||
|
```bash
|
||||||
|
# PostgreSQL에 연결
|
||||||
|
psql -h [host] -U [username] -d [database]
|
||||||
|
|
||||||
|
# schema.sql 실행
|
||||||
|
\i src/main/resources/sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**또는 직접 SQL 실행**:
|
||||||
|
```sql
|
||||||
|
-- batch_history 테이블 생성
|
||||||
|
CREATE TABLE IF NOT EXISTS public.batch_history (
|
||||||
|
uuid UUID PRIMARY KEY,
|
||||||
|
job VARCHAR(255) NOT NULL,
|
||||||
|
id VARCHAR(255) NOT NULL,
|
||||||
|
created_dttm TIMESTAMP NOT NULL,
|
||||||
|
updated_dttm TIMESTAMP NOT NULL,
|
||||||
|
status VARCHAR(50) NOT NULL,
|
||||||
|
completed_dttm TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스 생성
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_job ON public.batch_history(job);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_status ON public.batch_history(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_created ON public.batch_history(created_dttm DESC);
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 권한 설정** (필요한 경우):
|
||||||
|
```sql
|
||||||
|
-- 애플리케이션 사용자에게 권한 부여
|
||||||
|
GRANT ALL PRIVILEGES ON TABLE public.batch_history TO [app_user];
|
||||||
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO [app_user];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: 자동 초기화 활성화 (개발 환경만)
|
||||||
|
|
||||||
|
**application-local.yml 또는 application-dev.yml**:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: always
|
||||||
|
schema-locations: classpath:sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**주의**:
|
||||||
|
- 운영 환경에서는 `mode: never` 사용 권장
|
||||||
|
- 테이블이 이미 존재하면 권한 에러 발생 가능
|
||||||
|
|
||||||
|
## 테이블 구조
|
||||||
|
|
||||||
|
### batch_history
|
||||||
|
|
||||||
|
| 컬럼명 | 데이터 타입 | NULL | 설명 |
|
||||||
|
|--------|------------|------|------|
|
||||||
|
| uuid | UUID | NOT NULL | 배치 실행 고유 ID (Primary Key) |
|
||||||
|
| job | VARCHAR(255) | NOT NULL | 배치 작업 이름 |
|
||||||
|
| id | VARCHAR(255) | NOT NULL | 비즈니스 ID |
|
||||||
|
| created_dttm | TIMESTAMP | NOT NULL | 생성 일시 |
|
||||||
|
| updated_dttm | TIMESTAMP | NOT NULL | 수정 일시 |
|
||||||
|
| status | VARCHAR(50) | NOT NULL | 상태 (STARTED/COMPLETED/FAILED) |
|
||||||
|
| completed_dttm | TIMESTAMP | NULL | 완료 일시 |
|
||||||
|
|
||||||
|
**인덱스**:
|
||||||
|
- `idx_batch_history_job`: job 컬럼 인덱스
|
||||||
|
- `idx_batch_history_status`: status 컬럼 인덱스
|
||||||
|
- `idx_batch_history_created`: created_dttm 컬럼 인덱스 (DESC)
|
||||||
|
|
||||||
|
## 환경별 설정
|
||||||
|
|
||||||
|
### Local 환경
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://localhost:5432/kamco_local
|
||||||
|
username: dev_user
|
||||||
|
password: dev_password
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: always # 자동 초기화 활성화
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production 환경
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://prod-db:5432/kamco_prod
|
||||||
|
username: app_user
|
||||||
|
password: ${DB_PASSWORD}
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: never # 자동 초기화 비활성화
|
||||||
|
```
|
||||||
|
|
||||||
|
## 트러블슈팅
|
||||||
|
|
||||||
|
### 에러: "must be owner of table batch_history"
|
||||||
|
|
||||||
|
**원인**: 테이블이 이미 존재하지만, 현재 DB 사용자가 owner가 아님
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
1. SQL 자동 초기화 비활성화:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: never
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 또는 테이블 소유권 변경:
|
||||||
|
```sql
|
||||||
|
ALTER TABLE public.batch_history OWNER TO [app_user];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: "relation batch_history does not exist"
|
||||||
|
|
||||||
|
**원인**: batch_history 테이블이 생성되지 않음
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
1. SQL 스크립트 수동 실행:
|
||||||
|
```bash
|
||||||
|
psql -h [host] -U [user] -d [database] -f src/main/resources/sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 또는 자동 초기화 활성화 (개발 환경만):
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: always
|
||||||
|
schema-locations: classpath:sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: "permission denied for schema public"
|
||||||
|
|
||||||
|
**원인**: DB 사용자에게 public 스키마 권한 없음
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
```sql
|
||||||
|
GRANT ALL ON SCHEMA public TO [app_user];
|
||||||
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO [app_user];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 데이터베이스 마이그레이션
|
||||||
|
|
||||||
|
### 기존 환경에서 테이블 확인
|
||||||
|
```sql
|
||||||
|
-- batch_history 테이블 존재 확인
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'batch_history'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 테이블 구조 확인
|
||||||
|
\d public.batch_history
|
||||||
|
|
||||||
|
-- 인덱스 확인
|
||||||
|
\di public.idx_batch_history_*
|
||||||
|
```
|
||||||
|
|
||||||
|
### 테이블 재생성 (데이터 삭제 주의!)
|
||||||
|
```sql
|
||||||
|
-- 기존 테이블 삭제 (주의: 모든 데이터 손실!)
|
||||||
|
DROP TABLE IF EXISTS public.batch_history CASCADE;
|
||||||
|
|
||||||
|
-- schema.sql 재실행
|
||||||
|
\i src/main/resources/sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 백업 및 복구
|
||||||
|
|
||||||
|
### 테이블 백업
|
||||||
|
```bash
|
||||||
|
pg_dump -h [host] -U [user] -d [database] -t batch_history > batch_history_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 테이블 복구
|
||||||
|
```bash
|
||||||
|
psql -h [host] -U [user] -d [database] < batch_history_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 모니터링 쿼리
|
||||||
|
|
||||||
|
### 최근 배치 실행 이력 조회
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
uuid,
|
||||||
|
job,
|
||||||
|
id,
|
||||||
|
created_dttm,
|
||||||
|
completed_dttm,
|
||||||
|
status,
|
||||||
|
EXTRACT(EPOCH FROM (completed_dttm - created_dttm)) as duration_seconds
|
||||||
|
FROM public.batch_history
|
||||||
|
ORDER BY created_dttm DESC
|
||||||
|
LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 실패한 배치 조회
|
||||||
|
```sql
|
||||||
|
SELECT *
|
||||||
|
FROM public.batch_history
|
||||||
|
WHERE status = 'FAILED'
|
||||||
|
ORDER BY created_dttm DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 배치 작업별 성공률
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
job,
|
||||||
|
COUNT(*) as total_runs,
|
||||||
|
SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) as successful_runs,
|
||||||
|
ROUND(100.0 * SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
|
||||||
|
FROM public.batch_history
|
||||||
|
GROUP BY job
|
||||||
|
ORDER BY total_runs DESC;
|
||||||
|
```
|
||||||
@@ -57,11 +57,90 @@ git push
|
|||||||
|
|
||||||
### 2. Jenkins 서버 (폐쇄망 환경)
|
### 2. Jenkins 서버 (폐쇄망 환경)
|
||||||
|
|
||||||
|
#### Jenkins 파라미터
|
||||||
|
|
||||||
|
파이프라인 실행 시 다음 파라미터를 설정할 수 있습니다:
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 기본값 | 설명 |
|
||||||
|
|---------|------|--------|------|
|
||||||
|
| SPRING_PROFILES_ACTIVE | String | prod | Spring Profile (dev/prod) |
|
||||||
|
| BATCH_DATE | String | (공백) | Batch 날짜 (YYYY-MM-DD, 공백 = 오늘) |
|
||||||
|
| ADDITIONAL_PARAMS | String | (공백) | 추가 파라미터 (예: limit=100) |
|
||||||
|
| ACTION | Choice | RUN | 실행 모드 (RUN / VERIFY_ONLY) |
|
||||||
|
|
||||||
|
#### 실행 예시
|
||||||
|
|
||||||
|
**1. 기본 실행 (오늘 날짜)**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: prod
|
||||||
|
BATCH_DATE: (공백) ← 자동으로 오늘 날짜 사용
|
||||||
|
ADDITIONAL_PARAMS: (공백)
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=2026-02-09
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 특정 날짜로 실행**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: prod
|
||||||
|
BATCH_DATE: 2024-01-15
|
||||||
|
ADDITIONAL_PARAMS: (공백)
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=2024-01-15
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 오늘 날짜 + 추가 파라미터**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: prod
|
||||||
|
BATCH_DATE: (공백)
|
||||||
|
ADDITIONAL_PARAMS: limit=100 debug=true
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=2026-02-09 limit=100 debug=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. 개발 환경 테스트**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: dev
|
||||||
|
BATCH_DATE: 2024-02-01
|
||||||
|
ADDITIONAL_PARAMS: dryRun=true
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=dev date=2024-02-01 dryRun=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. JAR 검증만 (실행 안 함)**:
|
||||||
|
```
|
||||||
|
ACTION: VERIFY_ONLY
|
||||||
|
→ JAR 파일 존재만 확인하고 종료
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 날짜 파라미터 동작 방식
|
||||||
|
|
||||||
|
**BATCH_DATE 파라미터**:
|
||||||
|
- **비어있음 (기본값)**: Jenkins 서버의 현재 날짜 자동 사용 (YYYY-MM-DD)
|
||||||
|
- **값 입력**: 입력한 날짜 사용 (예: 2024-01-15)
|
||||||
|
|
||||||
|
**예시**:
|
||||||
|
```bash
|
||||||
|
# BATCH_DATE가 비어있으면
|
||||||
|
TODAY=$(date +%Y-%m-%d) # 2026-02-09
|
||||||
|
java -jar app.jar date=2026-02-09
|
||||||
|
|
||||||
|
# BATCH_DATE에 2024-01-15 입력하면
|
||||||
|
java -jar app.jar date=2024-01-15
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Jenkins 파이프라인 단계
|
||||||
|
|
||||||
Jenkins는 자동으로:
|
Jenkins는 자동으로:
|
||||||
1. Git에서 코드 체크아웃
|
1. **Environment 설정**: 오늘 날짜 계산 (TODAY 변수)
|
||||||
2. JAR 파일 존재 확인
|
2. **Checkout**: Git에서 코드 체크아웃
|
||||||
3. JAR 파일 정보 출력
|
3. **Verify JAR**: JAR 파일 존재 확인
|
||||||
4. 배포 실행 (Deploy stage에 배포 명령 추가 필요)
|
4. **Run JAR** (ACTION=RUN인 경우만):
|
||||||
|
- BATCH_DATE가 비어있으면 TODAY 사용
|
||||||
|
- JAR 실행 with 파라미터
|
||||||
|
|
||||||
## 파일 구조
|
## 파일 구조
|
||||||
|
|
||||||
@@ -90,14 +169,30 @@ out/
|
|||||||
## Jenkinsfile 구조
|
## Jenkinsfile 구조
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
|
parameters {
|
||||||
|
string(name: 'SPRING_PROFILES_ACTIVE', defaultValue: 'prod', ...)
|
||||||
|
string(name: 'BATCH_DATE', defaultValue: '', ...)
|
||||||
|
string(name: 'ADDITIONAL_PARAMS', defaultValue: '', ...)
|
||||||
|
choice(name: 'ACTION', choices: ['RUN', 'VERIFY_ONLY'], ...)
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
TODAY = sh(script: "date +%Y-%m-%d", returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
|
||||||
stage('Verify JAR') {
|
stage('Verify JAR') {
|
||||||
// JAR 파일 존재 확인
|
// JAR 파일 존재 확인
|
||||||
// 파일 정보 출력
|
// 파일 정보 출력
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Deploy') {
|
stage('Run JAR') {
|
||||||
// 배포 명령 실행
|
when {
|
||||||
// 예: scp, ssh, kubectl 등
|
expression { params.ACTION == 'RUN' }
|
||||||
|
}
|
||||||
|
// 날짜 결정: BATCH_DATE가 비어있으면 TODAY 사용
|
||||||
|
def batchDate = params.BATCH_DATE ?: env.TODAY
|
||||||
|
// JAR 실행
|
||||||
|
// java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=${batchDate} ${ADDITIONAL_PARAMS}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -38,70 +38,115 @@ public class ExportGeoJsonTasklet implements Tasklet {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("배치 작업 시작");
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
// 1. StepContext를 통해 바로 가져오기 (가장 추천)
|
// 1. StepContext를 통해 바로 가져오기 (가장 추천)
|
||||||
String jobName = chunkContext.getStepContext().getJobName();
|
String jobName = chunkContext.getStepContext().getJobName();
|
||||||
|
log.info("Job Name: {}", jobName);
|
||||||
|
|
||||||
// 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기
|
// 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기
|
||||||
|
log.info("진행중인 회차 목록 조회 중...");
|
||||||
List<AnalCntInfo> analList = repository.findAnalCntInfoList();
|
List<AnalCntInfo> analList = repository.findAnalCntInfoList();
|
||||||
|
log.info("진행중인 회차 수: {}", analList.size());
|
||||||
|
|
||||||
|
int processedAnalCount = 0;
|
||||||
for (AnalCntInfo info : analList) {
|
for (AnalCntInfo info : analList) {
|
||||||
|
log.info("----------------------------------------");
|
||||||
|
log.info("회차 처리 중: AnalUid={}, ResultUid={}", info.getAnalUid(), info.getResultUid());
|
||||||
|
log.info("전체 건수: {}, 파일 건수: {}", info.getAllCnt(), info.getFileCnt());
|
||||||
|
|
||||||
if (Objects.equals(info.getAllCnt(), info.getFileCnt())) {
|
if (Objects.equals(info.getAllCnt(), info.getFileCnt())) {
|
||||||
|
log.info("모든 파일이 이미 처리됨. 건너뜀.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//추론 ID
|
//추론 ID
|
||||||
String resultUid = info.getResultUid();
|
String resultUid = info.getResultUid();
|
||||||
|
log.info("ResultUid: {}", resultUid);
|
||||||
|
|
||||||
//insert 하기 jobname, resultUid , 시작시간
|
//insert 하기 jobname, resultUid , 시작시간
|
||||||
// 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기
|
// 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기
|
||||||
|
log.info("검수 완료된 도엽 목록 조회 중... (AnalUid={})", info.getAnalUid());
|
||||||
List<AnalMapSheetList> analMapList = repository.findCompletedAnalMapSheetList(info.getAnalUid());
|
List<AnalMapSheetList> analMapList = repository.findCompletedAnalMapSheetList(info.getAnalUid());
|
||||||
|
log.info("검수 완료된 도엽 수: {}", analMapList.size());
|
||||||
|
|
||||||
//TODO 도엽이 4개이상 존재할때 만 RUN 하기
|
//TODO 도엽이 4개이상 존재할때 만 RUN 하기
|
||||||
if (analMapList.isEmpty()) {
|
if (analMapList.isEmpty()) {
|
||||||
|
log.warn("검수 완료된 도엽이 없음. 건너뜀.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//insert 하기 jobname, resultUid , 시작시간
|
//insert 하기 jobname, resultUid , 시작시간
|
||||||
boolean anyProcessed = false;
|
boolean anyProcessed = false;
|
||||||
|
int processedMapSheetCount = 0;
|
||||||
|
int totalGeoJsonFiles = 0;
|
||||||
|
|
||||||
for (AnalMapSheetList mapSheet : analMapList) {
|
for (AnalMapSheetList mapSheet : analMapList) {
|
||||||
|
log.info(" 도엽 처리 중: MapSheetNum={}", mapSheet.getMapSheetNum());
|
||||||
|
|
||||||
//도엽별 geom 데이터 가지고 와서 geojson 만들기
|
//도엽별 geom 데이터 가지고 와서 geojson 만들기
|
||||||
List<CompleteLabelData> completeList =
|
List<CompleteLabelData> completeList =
|
||||||
repository.findCompletedYesterdayLabelingList(
|
repository.findCompletedYesterdayLabelingList(
|
||||||
info.getAnalUid(), mapSheet.getMapSheetNum());
|
info.getAnalUid(), mapSheet.getMapSheetNum());
|
||||||
|
log.info(" 완료된 라벨링 데이터 수: {}", completeList.size());
|
||||||
|
|
||||||
if (!completeList.isEmpty()) {
|
if (!completeList.isEmpty()) {
|
||||||
List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList();
|
List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList();
|
||||||
|
log.info(" GeoUID 목록 생성 완료: {} 건", geoUids.size());
|
||||||
|
|
||||||
List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList();
|
List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList();
|
||||||
|
log.info(" GeoJSON Feature 변환 완료: {} 개", features.size());
|
||||||
|
|
||||||
FeatureCollection collection = new FeatureCollection(features);
|
FeatureCollection collection = new FeatureCollection(features);
|
||||||
String filename = mapSheet.buildFilename(resultUid);
|
String filename = mapSheet.buildFilename(resultUid);
|
||||||
|
log.info(" GeoJSON 파일명: {}", filename);
|
||||||
|
|
||||||
// 형식 /kamco-nfs/dataset/request/uuid/filename
|
// 형식 /kamco-nfs/dataset/request/uuid/filename
|
||||||
Path outputPath = Paths.get(trainingDataDir + File.separator + "request" + File.separator + resultUid, filename);
|
Path outputPath = Paths.get(trainingDataDir + File.separator + "request" + File.separator + resultUid, filename);
|
||||||
|
log.info(" 출력 경로: {}", outputPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(outputPath.getParent());
|
Files.createDirectories(outputPath.getParent());
|
||||||
|
log.info(" 디렉토리 생성 완료: {}", outputPath.getParent());
|
||||||
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
objectMapper.writeValue(outputPath.toFile(), collection);
|
objectMapper.writeValue(outputPath.toFile(), collection);
|
||||||
|
log.info(" GeoJSON 파일 저장 완료: {}", outputPath);
|
||||||
|
|
||||||
repository.updateLearnDataGeomFileCreateYn(geoUids);
|
repository.updateLearnDataGeomFileCreateYn(geoUids);
|
||||||
|
log.info(" DB 업데이트 완료: {} 건", geoUids.size());
|
||||||
|
|
||||||
anyProcessed = true;
|
anyProcessed = true;
|
||||||
|
processedMapSheetCount++;
|
||||||
|
totalGeoJsonFiles++;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error(e.getMessage());
|
log.error(" GeoJSON 파일 생성 실패: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("회차 처리 완료: ResultUid={}", resultUid);
|
||||||
|
log.info(" 처리된 도엽 수: {}", processedMapSheetCount);
|
||||||
|
log.info(" 생성된 GeoJSON 파일 수: {}", totalGeoJsonFiles);
|
||||||
|
|
||||||
if (anyProcessed) {
|
if (anyProcessed) {
|
||||||
|
log.info("Docker 컨테이너 실행 중... (ResultUid={})", resultUid);
|
||||||
dockerRunnerService.run(resultUid);
|
dockerRunnerService.run(resultUid);
|
||||||
|
log.info("Docker 컨테이너 실행 완료 (ResultUid={})", resultUid);
|
||||||
|
processedAnalCount++;
|
||||||
|
} else {
|
||||||
|
log.warn("처리된 도엽이 없어 Docker 실행 건너뜀 (ResultUid={})", resultUid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("배치 작업 완료");
|
||||||
|
log.info("처리된 회차 수: {}", processedAnalCount);
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
return RepeatStatus.FINISHED;
|
return RepeatStatus.FINISHED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package com.kamco.cd.geojsonscheduler.listener;
|
package com.kamco.cd.geojsonscheduler.listener;
|
||||||
|
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.springframework.batch.core.JobExecution;
|
import org.springframework.batch.core.JobExecution;
|
||||||
import org.springframework.batch.core.JobExecutionListener;
|
import org.springframework.batch.core.JobExecutionListener;
|
||||||
import org.springframework.batch.core.BatchStatus;
|
import org.springframework.batch.core.BatchStatus;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
@Component
|
@Component
|
||||||
public class BatchHistoryListener implements JobExecutionListener {
|
public class BatchHistoryListener implements JobExecutionListener {
|
||||||
|
|
||||||
@@ -18,8 +21,13 @@ public class BatchHistoryListener implements JobExecutionListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeJob(JobExecution jobExecution) {
|
public void beforeJob(JobExecution jobExecution) {
|
||||||
|
log.info("=========================================================");
|
||||||
|
log.info("배치 Job 시작 - BatchHistoryListener");
|
||||||
|
log.info("=========================================================");
|
||||||
|
|
||||||
// 1. UUID 생성 (또는 파라미터에서 가져오기)
|
// 1. UUID 생성 (또는 파라미터에서 가져오기)
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
|
log.info("배치 UUID 생성: {}", uuid);
|
||||||
|
|
||||||
// 2. JobExecutionContext에 UUID 저장 (afterJob에서 쓰기 위해)
|
// 2. JobExecutionContext에 UUID 저장 (afterJob에서 쓰기 위해)
|
||||||
jobExecution.getExecutionContext().put("batch_uuid", uuid);
|
jobExecution.getExecutionContext().put("batch_uuid", uuid);
|
||||||
@@ -27,22 +35,56 @@ public class BatchHistoryListener implements JobExecutionListener {
|
|||||||
// 3. Job 이름과 비즈니스 ID(파라미터 등) 가져오기
|
// 3. Job 이름과 비즈니스 ID(파라미터 등) 가져오기
|
||||||
String jobName = jobExecution.getJobInstance().getJobName();
|
String jobName = jobExecution.getJobInstance().getJobName();
|
||||||
String businessId = jobExecution.getJobParameters().getString("id", "UNKNOWN"); // 파라미터 'id'가 있다고 가정
|
String businessId = jobExecution.getJobParameters().getString("id", "UNKNOWN"); // 파라미터 'id'가 있다고 가정
|
||||||
|
log.info("Job Name: {}", jobName);
|
||||||
|
log.info("Business ID: {}", businessId);
|
||||||
|
log.info("Job Instance ID: {}", jobExecution.getJobInstance().getInstanceId());
|
||||||
|
log.info("Job Execution ID: {}", jobExecution.getId());
|
||||||
|
|
||||||
// 4. 시작 기록
|
// 4. 시작 기록
|
||||||
|
log.info("batch_history 테이블에 시작 기록 저장 중...");
|
||||||
batchHistoryService.startBatch(uuid, jobName, businessId);
|
batchHistoryService.startBatch(uuid, jobName, businessId);
|
||||||
|
log.info("배치 시작 기록 저장 완료");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterJob(JobExecution jobExecution) {
|
public void afterJob(JobExecution jobExecution) {
|
||||||
|
log.info("=========================================================");
|
||||||
|
log.info("배치 Job 종료 - BatchHistoryListener");
|
||||||
|
log.info("=========================================================");
|
||||||
|
|
||||||
// 1. 저장해둔 UUID 꺼내기
|
// 1. 저장해둔 UUID 꺼내기
|
||||||
UUID uuid = (UUID) jobExecution.getExecutionContext().get("batch_uuid");
|
UUID uuid = (UUID) jobExecution.getExecutionContext().get("batch_uuid");
|
||||||
|
log.info("배치 UUID: {}", uuid);
|
||||||
|
|
||||||
// 2. 성공 여부 판단 (COMPLETED면 성공, 그 외 실패)
|
// 2. 성공 여부 판단 (COMPLETED면 성공, 그 외 실패)
|
||||||
boolean isSuccess = jobExecution.getStatus() == BatchStatus.COMPLETED;
|
boolean isSuccess = jobExecution.getStatus() == BatchStatus.COMPLETED;
|
||||||
|
log.info("배치 상태: {}", jobExecution.getStatus());
|
||||||
|
log.info("배치 성공 여부: {}", isSuccess ? "성공" : "실패");
|
||||||
|
|
||||||
|
if (jobExecution.getStatus() == BatchStatus.FAILED) {
|
||||||
|
log.error("배치 실행 실패!");
|
||||||
|
jobExecution.getAllFailureExceptions().forEach(t ->
|
||||||
|
log.error("실패 원인: {}", t.getMessage(), t)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실행 시간 계산
|
||||||
|
if (jobExecution.getStartTime() != null && jobExecution.getEndTime() != null) {
|
||||||
|
Duration duration = Duration.between(jobExecution.getStartTime(), jobExecution.getEndTime());
|
||||||
|
long seconds = duration.getSeconds();
|
||||||
|
long millis = duration.toMillis();
|
||||||
|
log.info("배치 실행 시간: {} ms ({} 초)", millis, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 종료 기록
|
// 3. 종료 기록
|
||||||
if (uuid != null) {
|
if (uuid != null) {
|
||||||
|
log.info("batch_history 테이블에 종료 기록 저장 중...");
|
||||||
batchHistoryService.finishBatch(uuid, isSuccess);
|
batchHistoryService.finishBatch(uuid, isSuccess);
|
||||||
|
log.info("배치 종료 기록 저장 완료");
|
||||||
|
} else {
|
||||||
|
log.warn("배치 UUID가 없어 종료 기록을 저장할 수 없습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("=========================================================");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kamco.cd.geojsonscheduler.listener;
|
package com.kamco.cd.geojsonscheduler.listener;
|
||||||
|
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -8,6 +9,7 @@ import java.sql.Timestamp;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
@Service
|
@Service
|
||||||
public class BatchHistoryService {
|
public class BatchHistoryService {
|
||||||
|
|
||||||
@@ -22,16 +24,22 @@ public class BatchHistoryService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void startBatch(UUID uuid, String jobName, String businessId) {
|
public void startBatch(UUID uuid, String jobName, String businessId) {
|
||||||
|
log.info("[BatchHistoryService] 배치 시작 기록 저장");
|
||||||
|
log.info(" UUID: {}", uuid);
|
||||||
|
log.info(" Job Name: {}", jobName);
|
||||||
|
log.info(" Business ID: {}", businessId);
|
||||||
|
|
||||||
String sql = """
|
String sql = """
|
||||||
INSERT INTO public.batch_history
|
INSERT INTO public.batch_history
|
||||||
(uuid, job, id, created_dttm, updated_dttm, status)
|
(uuid, job, id, created_dttm, updated_dttm, status)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""";
|
""";
|
||||||
|
|
||||||
Timestamp now = Timestamp.valueOf(LocalDateTime.now());
|
Timestamp now = Timestamp.valueOf(LocalDateTime.now());
|
||||||
|
log.info(" 시작 시간: {}", now);
|
||||||
|
|
||||||
// 초기 상태는 'STARTED'로 저장
|
// 초기 상태는 'STARTED'로 저장
|
||||||
jdbcTemplate.update(sql,
|
int rowsAffected = jdbcTemplate.update(sql,
|
||||||
uuid,
|
uuid,
|
||||||
jobName,
|
jobName,
|
||||||
businessId,
|
businessId,
|
||||||
@@ -39,6 +47,8 @@ public class BatchHistoryService {
|
|||||||
now, // updated_dttm
|
now, // updated_dttm
|
||||||
"STARTED"
|
"STARTED"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
log.info("[BatchHistoryService] 배치 시작 기록 저장 완료 ({} rows affected)", rowsAffected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,22 +56,34 @@ public class BatchHistoryService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void finishBatch(UUID uuid, boolean isSuccess) {
|
public void finishBatch(UUID uuid, boolean isSuccess) {
|
||||||
|
log.info("[BatchHistoryService] 배치 종료 기록 업데이트");
|
||||||
|
log.info(" UUID: {}", uuid);
|
||||||
|
log.info(" 성공 여부: {}", isSuccess);
|
||||||
|
|
||||||
String sql = """
|
String sql = """
|
||||||
UPDATE public.batch_history
|
UPDATE public.batch_history
|
||||||
SET status = ?,
|
SET status = ?,
|
||||||
updated_dttm = ?,
|
updated_dttm = ?,
|
||||||
completed_dttm = ?
|
completed_dttm = ?
|
||||||
WHERE uuid = ?
|
WHERE uuid = ?
|
||||||
""";
|
""";
|
||||||
|
|
||||||
Timestamp now = Timestamp.valueOf(LocalDateTime.now());
|
Timestamp now = Timestamp.valueOf(LocalDateTime.now());
|
||||||
String status = isSuccess ? "COMPLETED" : "FAILED";
|
String status = isSuccess ? "COMPLETED" : "FAILED";
|
||||||
|
log.info(" 완료 시간: {}", now);
|
||||||
|
log.info(" 최종 상태: {}", status);
|
||||||
|
|
||||||
jdbcTemplate.update(sql,
|
int rowsAffected = jdbcTemplate.update(sql,
|
||||||
status,
|
status,
|
||||||
now, // updated_dttm (마지막 변경 시간)
|
now, // updated_dttm (마지막 변경 시간)
|
||||||
now, // completed_dttm (완료 시간)
|
now, // completed_dttm (완료 시간)
|
||||||
uuid
|
uuid
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (rowsAffected > 0) {
|
||||||
|
log.info("[BatchHistoryService] 배치 종료 기록 업데이트 완료 ({} rows affected)", rowsAffected);
|
||||||
|
} else {
|
||||||
|
log.warn("[BatchHistoryService] 업데이트된 row가 없습니다. UUID가 존재하지 않을 수 있습니다: {}", uuid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ public class TrainingDataReviewJobRepository {
|
|||||||
public void updateLearnDataGeomFileCreateYn(List<Long> geoUids) {
|
public void updateLearnDataGeomFileCreateYn(List<Long> geoUids) {
|
||||||
String placeholders = geoUids.stream().map(id -> "?").collect(Collectors.joining(","));
|
String placeholders = geoUids.stream().map(id -> "?").collect(Collectors.joining(","));
|
||||||
String sql =
|
String sql =
|
||||||
"UPDATE tb_map_sheet_learn_data_geom SET file_create_yn = true, modified_date = NOW()"
|
"UPDATE tb_map_sheet_learn_data_geom SET file_create_yn = true, updated_dttm = NOW()"
|
||||||
+ " WHERE geo_uid IN ("
|
+ " WHERE geo_uid IN ("
|
||||||
+ placeholders
|
+ placeholders
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|||||||
@@ -11,10 +11,18 @@ spring:
|
|||||||
connection-timeout: 20000
|
connection-timeout: 20000
|
||||||
idle-timeout: 300000
|
idle-timeout: 300000
|
||||||
max-lifetime: 1800000
|
max-lifetime: 1800000
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: never
|
||||||
|
# schema-locations: classpath:sql/schema.sql
|
||||||
|
# Note: batch_history 테이블이 이미 존재하므로 SQL 초기화 비활성화
|
||||||
|
# 새 환경에서 테이블 생성이 필요한 경우, 아래 SQL을 수동으로 실행:
|
||||||
|
# src/main/resources/sql/schema.sql
|
||||||
batch:
|
batch:
|
||||||
job:
|
job:
|
||||||
enabled: true
|
enabled: true
|
||||||
initialize-schema: never
|
jdbc:
|
||||||
|
initialize-schema: always
|
||||||
|
|
||||||
training-data:
|
training-data:
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
-- batch_history 테이블 생성
|
||||||
|
CREATE TABLE IF NOT EXISTS public.batch_history (
|
||||||
|
uuid UUID PRIMARY KEY,
|
||||||
|
job VARCHAR(255) NOT NULL,
|
||||||
|
id VARCHAR(255) NOT NULL,
|
||||||
|
created_dttm TIMESTAMP NOT NULL,
|
||||||
|
updated_dttm TIMESTAMP NOT NULL,
|
||||||
|
status VARCHAR(50) NOT NULL,
|
||||||
|
completed_dttm TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스 생성 (조회 성능 향상)
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_job ON public.batch_history(job);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_status ON public.batch_history(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_created ON public.batch_history(created_dttm DESC);
|
||||||
|
|
||||||
|
-- 코멘트
|
||||||
|
COMMENT ON TABLE public.batch_history IS '배치 작업 실행 이력';
|
||||||
|
COMMENT ON COLUMN public.batch_history.uuid IS '배치 실행 고유 ID';
|
||||||
|
COMMENT ON COLUMN public.batch_history.job IS '배치 작업 이름';
|
||||||
|
COMMENT ON COLUMN public.batch_history.id IS '비즈니스 ID';
|
||||||
|
COMMENT ON COLUMN public.batch_history.created_dttm IS '생성 일시';
|
||||||
|
COMMENT ON COLUMN public.batch_history.updated_dttm IS '수정 일시';
|
||||||
|
COMMENT ON COLUMN public.batch_history.status IS '상태 (STARTED/COMPLETED/FAILED)';
|
||||||
|
COMMENT ON COLUMN public.batch_history.completed_dttm IS '완료 일시';
|
||||||
Reference in New Issue
Block a user