From 090f009529bc7012be470f0d26f1e7dead95f10f Mon Sep 17 00:00:00 2001 From: hyojin kim Date: Wed, 10 Dec 2025 15:59:47 +0900 Subject: [PATCH] =?UTF-8?q?:sparkles:=20ShipDetailUpdateJob=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20-=20CrewList=20-=20StowageCommodity=20-=20GroupBene?= =?UTF-8?q?ficialOwnerHistory=20-=20ShipManagerHistory=20-=20OperatorHisto?= =?UTF-8?q?ry=20-=20TechnicalManagerHistory=20-=20BareBoatCharterHistory?= =?UTF-8?q?=20-=20NameHistory=20-=20FlagHistory=20-=20AdditionalInformatio?= =?UTF-8?q?n=20-=20PandIHistory=20-=20CallSignAndMmsiHistory=20-=20IceClas?= =?UTF-8?q?s=20-=20SafetyManagementCertificateHistory=20-=20ClassHistory?= =?UTF-8?q?=20-=20SurveyDatesHistory=20-=20SurveyDatesHistoryUnique=20-=20?= =?UTF-8?q?SisterShipLinks=20-=20StatusHistory=20-=20SpecialFeature=20-=20?= =?UTF-8?q?Thrusters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../batch/common/util/JsonChangeDetector.java | 90 +- .../global/model/BatchLastExecution.java | 38 + .../BatchLastExecutionRepository.java | 36 + .../config/ShipDetailUpdateJobConfig.java | 118 +++ .../batch/dto/AdditionalInformationDto.java | 45 + .../batch/dto/BareBoatCharterHistoryDto.java | 30 + .../batch/dto/CallSignAndMmsiHistoryDto.java | 32 + .../shipdetail/batch/dto/ClassHistoryDto.java | 37 + .../shipdetail/batch/dto/CrewListDto.java | 61 ++ .../shipdetail/batch/dto/FlagHistoryDto.java | 31 + .../dto/GroupBeneficialOwnerHistoryDto.java | 45 + .../shipdetail/batch/dto/IceClassDto.java | 27 + .../shipdetail/batch/dto/NameHistoryDto.java | 29 + .../batch/dto/OperatorHistoryDto.java | 43 + .../shipdetail/batch/dto/PandIHistoryDto.java | 33 + ...SafetyManagementCertificateHistoryDto.java | 49 ++ .../shipdetail/batch/dto/ShipDetailDto.java | 213 +++-- .../batch/dto/ShipDetailUpdate.java | 26 +- .../jobs/shipdetail/batch/dto/ShipDto.java | 34 + .../batch/dto/ShipManagerHistoryDto.java | 45 + .../batch/dto/ShipUpdateApiResponse.java | 23 + .../batch/dto/SisterShipLinksDto.java | 25 + .../batch/dto/SpecialFeatureDto.java | 29 + .../batch/dto/StatusHistoryDto.java | 31 + .../batch/dto/StowageCommodityDto.java | 43 + .../batch/dto/SurveyDatesHistoryDto.java | 37 + .../dto/SurveyDatesHistoryUniqueDto.java | 31 + .../batch/dto/TechnicalManagerHistoryDto.java | 32 + .../shipdetail/batch/dto/ThrustersDto.java | 39 + .../entity/AdditionalInformationEntity.java | 29 + .../entity/BareBoatCharterHistoryEntity.java | 21 + .../entity/CallSignAndMmsiHistoryEntity.java | 22 + .../batch/entity/ClassHistoryEntity.java | 25 + .../batch/entity/CrewListEntity.java | 31 + .../batch/entity/FlagHistoryEntity.java | 22 + .../GroupBeneficialOwnerHistoryEntity.java | 28 + .../batch/entity/IceClassEntity.java | 20 + .../batch/entity/NameHistoryEntity.java | 21 + .../batch/entity/OperatorHistoryEntity.java | 23 + .../batch/entity/PandIHistoryEntity.java | 23 + ...etyManagementCertificateHistoryEntity.java | 31 + .../entity/ShipManagerHistoryEntity.java | 28 + .../batch/entity/SisterShipLinksEntity.java | 19 + .../batch/entity/SpecialFeatureEntity.java | 21 + .../batch/entity/StatusHistoryEntity.java | 22 + .../batch/entity/StowageCommodityEntity.java | 28 + .../entity/SurveyDatesHistoryEntity.java | 25 + .../SurveyDatesHistoryUniqueEntity.java | 22 + .../entity/TechnicalManagerHistoryEntity.java | 22 + .../batch/entity/ThrustersEntity.java | 26 + .../processor/ShipDetailDataProcessor.java | 554 +++++++++++- .../reader/ShipDetailUpdateDataReader.java | 309 +++++++ .../repository/ShipDetailRepository.java | 66 +- .../repository/ShipDetailRepositoryImpl.java | 817 +++++++++++++++++- .../batch/repository/ShipDetailSql.java | 491 +++++++++++ .../batch/writer/ShipDetailDataWriter.java | 146 +++- .../snp/batch/service/BatchDateService.java | 66 ++ 57 files changed, 4177 insertions(+), 133 deletions(-) create mode 100644 src/main/java/com/snp/batch/global/model/BatchLastExecution.java create mode 100644 src/main/java/com/snp/batch/global/repository/BatchLastExecutionRepository.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/AdditionalInformationDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/BareBoatCharterHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CallSignAndMmsiHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ClassHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CrewListDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/FlagHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/GroupBeneficialOwnerHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/IceClassDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/NameHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/OperatorHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/PandIHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SafetyManagementCertificateHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipManagerHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipUpdateApiResponse.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SisterShipLinksDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SpecialFeatureDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StatusHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StowageCommodityDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryUniqueDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/TechnicalManagerHistoryDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ThrustersDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/AdditionalInformationEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/BareBoatCharterHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CallSignAndMmsiHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ClassHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CrewListEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/FlagHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/GroupBeneficialOwnerHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/IceClassEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/NameHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/OperatorHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/PandIHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SafetyManagementCertificateHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipManagerHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SisterShipLinksEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SpecialFeatureEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StatusHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StowageCommodityEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryUniqueEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/TechnicalManagerHistoryEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ThrustersEntity.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/reader/ShipDetailUpdateDataReader.java create mode 100644 src/main/java/com/snp/batch/service/BatchDateService.java diff --git a/src/main/java/com/snp/batch/common/util/JsonChangeDetector.java b/src/main/java/com/snp/batch/common/util/JsonChangeDetector.java index 96231f1..f600491 100644 --- a/src/main/java/com/snp/batch/common/util/JsonChangeDetector.java +++ b/src/main/java/com/snp/batch/common/util/JsonChangeDetector.java @@ -15,12 +15,38 @@ public class JsonChangeDetector { private static final java.util.Set EXCLUDE_KEYS = java.util.Set.of("DataSetVersion", "APSStatus", "LastUpdateDate", "LastUpdateDateTime"); - private static final Map LIST_SORT_KEYS = Map.of( - // List 필드명 // 정렬 기준 키 - "OwnerHistory" ,"Sequence", // OwnerHistory는 Sequence를 기준으로 정렬 - "SurveyDatesHistoryUnique" , "SurveyDate" // SurveyDatesHistoryUnique는 SurveyDate를 기준으로 정렬 - // 추가적인 List/Array 필드가 있다면 여기에 추가 - ); + // ========================================================================= + // ✅ LIST_SORT_KEYS: 정적 초기화 블록을 사용한 Map 정의 + // ========================================================================= + private static final Map LIST_SORT_KEYS; + static { + // TreeMap을 사용하여 키를 알파벳 순으로 정렬할 수도 있지만, 여기서는 HashMap을 사용하고 final로 만듭니다. + Map map = new HashMap<>(); + // List 필드명 // 정렬 기준 복합 키 (JSON 필드명, 쉼표로 구분) + map.put("OwnerHistory", "OwnerCode,EffectiveDate"); + map.put("CrewList", "ID"); + map.put("StowageCommodity", "Sequence,CommodityCode,StowageCode"); + map.put("GroupBeneficialOwnerHistory", "EffectiveDate,GroupBeneficialOwnerCode,Sequence"); + map.put("ShipManagerHistory", "EffectiveDate,ShipManagerCode,Sequence"); + map.put("OperatorHistory", "EffectiveDate,OperatorCode,Sequence"); + map.put("TechnicalManagerHistory", "EffectiveDate,Sequence,TechnicalManagerCode"); + map.put("BareBoatCharterHistory", "Sequence,EffectiveDate,BBChartererCode"); + map.put("NameHistory", "Sequence,EffectiveDate"); + map.put("FlagHistory", "FlagCode,EffectiveDate,Sequence"); + map.put("PandIHistory", "PandIClubCode,EffectiveDate"); + map.put("CallSignAndMmsiHistory", "EffectiveDate,SeqNo"); + map.put("IceClass", "IceClassCode"); + map.put("SafetyManagementCertificateHistory", "Sequence"); + map.put("ClassHistory", "ClassCode,EffectiveDate,Sequence"); + map.put("SurveyDatesHistory", "ClassSocietyCode"); + map.put("SurveyDatesHistoryUnique", "ClassSocietyCode,SurveyDate,SurveyType"); + map.put("SisterShipLinks", "LinkedLRNO"); + map.put("StatusHistory", "Sequence,StatusCode,StatusDate"); + map.put("SpecialFeature", "Sequence,SpecialFeatureCode"); + map.put("Thrusters", "Sequence"); + + LIST_SORT_KEYS = Collections.unmodifiableMap(map); + } // ========================================================================= // 1. JSON 문자열을 정렬 및 필터링된 Map으로 변환하는 핵심 로직 @@ -90,14 +116,16 @@ public class JsonChangeDetector { } } - // 2. 🔑 List 필드명에 따른 순서 정렬 로직 (추가된 핵심 로직) + // 2. 🔑 List 필드명에 따른 복합 순서 정렬 로직 (수정된 핵심 로직) String listFieldName = entry.getKey(); - String sortKey = LIST_SORT_KEYS.get(listFieldName); + String sortKeysString = LIST_SORT_KEYS.get(listFieldName); // 쉼표로 구분된 복합 키 문자열 + + if (sortKeysString != null && !filteredList.isEmpty() && filteredList.get(0) instanceof Map) { + // 복합 키 문자열을 개별 키 배열로 분리 + final String[] sortKeys = sortKeysString.split(","); - if (sortKey != null && !filteredList.isEmpty() && filteredList.get(0) instanceof Map) { // Map 요소를 가진 리스트인 경우에만 정렬 실행 try { - // 정렬 기준 키를 사용하여 Comparator를 생성 Collections.sort(filteredList, new Comparator() { @Override @SuppressWarnings("unchecked") @@ -105,22 +133,45 @@ public class JsonChangeDetector { Map map1 = (Map) o1; Map map2 = (Map) o2; - // 정렬 기준 키(sortKey)의 값을 가져와 비교 - Object key1 = map1.get(sortKey); - Object key2 = map2.get(sortKey); + // 복합 키(sortKeys)를 순서대로 순회하며 비교 + for (String rawSortKey : sortKeys) { + // 키의 공백 제거 + String sortKey = rawSortKey.trim(); - if (key1 == null || key2 == null) { - // 키 값이 null인 경우, Map의 전체 문자열로 비교 (안전장치) - return map1.toString().compareTo(map2.toString()); + Object key1 = map1.get(sortKey); + Object key2 = map2.get(sortKey); + + // null 값 처리 로직 + if (key1 == null && key2 == null) { + continue; // 두 값이 동일하므로 다음 키로 이동 + } + if (key1 == null) { + // key1이 null이고 key2는 null이 아니면, key2가 더 크다고 (뒤 순서) 간주하고 1 반환 + return 1; + } + if (key2 == null) { + // key2가 null이고 key1은 null이 아니면, key1이 더 크다고 (뒤 순서) 간주하고 -1 반환 + return -1; + } + + // 값을 문자열로 변환하여 비교 (String, Number, Date 타입 모두 처리 가능) + int comparisonResult = key1.toString().compareTo(key2.toString()); + + // 현재 키에서 순서가 결정되면 즉시 반환 + if (comparisonResult != 0) { + return comparisonResult; + } + // comparisonResult == 0 이면 다음 키로 이동하여 비교를 계속함 } - // String 타입으로 변환하여 비교 (Date, Number 타입도 대부분 String으로 처리 가능) - return key1.toString().compareTo(key2.toString()); + // 모든 키를 비교해도 동일한 경우 + // 이 경우 두 Map은 해시값 측면에서 동일한 것으로 간주되어야 합니다. + return 0; } }); } catch (Exception e) { System.err.println("List sort failed for key " + listFieldName + ": " + e.getMessage()); - // 정렬 실패 시 원래 순서 유지 + // 정렬 실패 시 원래 순서 유지 (filteredList 상태 유지) } } sortedMap.put(key, filteredList); @@ -132,7 +183,6 @@ public class JsonChangeDetector { return sortedMap; } - // ========================================================================= // 2. 해시 생성 로직 // ========================================================================= diff --git a/src/main/java/com/snp/batch/global/model/BatchLastExecution.java b/src/main/java/com/snp/batch/global/model/BatchLastExecution.java new file mode 100644 index 0000000..6158776 --- /dev/null +++ b/src/main/java/com/snp/batch/global/model/BatchLastExecution.java @@ -0,0 +1,38 @@ +package com.snp.batch.global.model; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.*; +import java.time.LocalDate; +import java.time.LocalDateTime; +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "BATCH_LAST_EXECUTION") +@EntityListeners(AuditingEntityListener.class) +public class BatchLastExecution { + @Id + @Column(name = "API_KEY", length = 50) + private String apiKey; + + @Column(name = "LAST_SUCCESS_DATE", nullable = false) + private LocalDate lastSuccessDate; + + @CreatedDate + @Column(name = "CREATED_AT", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "UPDATED_AT", nullable = false) + private LocalDateTime updatedAt; + + public BatchLastExecution(String apiKey, LocalDate lastSuccessDate) { + this.apiKey = apiKey; + this.lastSuccessDate = lastSuccessDate; + } +} diff --git a/src/main/java/com/snp/batch/global/repository/BatchLastExecutionRepository.java b/src/main/java/com/snp/batch/global/repository/BatchLastExecutionRepository.java new file mode 100644 index 0000000..d62e7d3 --- /dev/null +++ b/src/main/java/com/snp/batch/global/repository/BatchLastExecutionRepository.java @@ -0,0 +1,36 @@ +package com.snp.batch.global.repository; + +import com.snp.batch.global.model.BatchLastExecution; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.Optional; + +@Repository +public interface BatchLastExecutionRepository extends JpaRepository { + // 1. findLastSuccessDate 함수 구현 + /** + * API 키를 기준으로 마지막 성공 일자를 조회합니다. + * @param apiKey 조회할 API 키 (예: "SHIP_UPDATE_API") + * @return 마지막 성공 일자 (LocalDate)를 포함하는 Optional + */ + @Query("SELECT b.lastSuccessDate FROM BatchLastExecution b WHERE b.apiKey = :apiKey") + Optional findLastSuccessDate(@Param("apiKey") String apiKey); + + + // 2. updateLastSuccessDate 함수 구현 (직접 UPDATE 쿼리 사용) + /** + * 특정 API 키의 마지막 성공 일자를 업데이트합니다. + * + * @param apiKey 업데이트할 API 키 + * @param successDate 업데이트할 성공 일자 + * @return 업데이트된 레코드 수 + */ + @Modifying + @Query("UPDATE BatchLastExecution b SET b.lastSuccessDate = :successDate WHERE b.apiKey = :apiKey") + int updateLastSuccessDate(@Param("apiKey") String apiKey, @Param("successDate") LocalDate successDate); +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java new file mode 100644 index 0000000..20d345c --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/config/ShipDetailUpdateJobConfig.java @@ -0,0 +1,118 @@ +package com.snp.batch.jobs.shipdetail.batch.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.snp.batch.common.batch.config.BaseJobConfig; +import com.snp.batch.jobs.shipdetail.batch.dto.ShipDetailComparisonData; +import com.snp.batch.jobs.shipdetail.batch.dto.ShipDetailUpdate; +import com.snp.batch.jobs.shipdetail.batch.processor.ShipDetailDataProcessor; +import com.snp.batch.jobs.shipdetail.batch.reader.ShipDetailUpdateDataReader; +import com.snp.batch.jobs.shipdetail.batch.writer.ShipDetailDataWriter; +import com.snp.batch.service.BatchDateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemWriter; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * 선박 상세 정보 Import Job Config + * + * 특징: + * - ship_data 테이블에서 IMO 번호 조회 + * - IMO 번호를 100개씩 배치로 분할 + * - Maritime API GetShipsByIHSLRorIMONumbers 호출 + * TODO : GetShipsByIHSLRorIMONumbersAll 호출로 변경 + * - 선박 상세 정보를 ship_detail 테이블에 저장 (UPSERT) + * + * 데이터 흐름: + * ShipDetailDataReader (ship_data → Maritime API) + * ↓ (ShipDetailDto) + * ShipDetailDataProcessor + * ↓ (ShipDetailEntity) + * ShipDetailDataWriter + * ↓ (ship_detail 테이블) + */ + +/** + * 선박 상세 정보 Import Job Config + * I: ShipDetailComparisonData (Reader 출력) + * O: ShipDetailUpdate (Processor 출력) + */ +@Slf4j +@Configuration +public class ShipDetailUpdateJobConfig extends BaseJobConfig { + + private final ShipDetailDataProcessor shipDetailDataProcessor; + private final ShipDetailDataWriter shipDetailDataWriter; + private final JdbcTemplate jdbcTemplate; + private final WebClient maritimeApiWebClient; + private final ObjectMapper objectMapper; // ObjectMapper 주입 추가 + private final BatchDateService batchDateService; + + public ShipDetailUpdateJobConfig( + JobRepository jobRepository, + PlatformTransactionManager transactionManager, + ShipDetailDataProcessor shipDetailDataProcessor, + ShipDetailDataWriter shipDetailDataWriter, + JdbcTemplate jdbcTemplate, + @Qualifier("maritimeApiWebClient") WebClient maritimeApiWebClient, + ObjectMapper objectMapper, + BatchDateService batchDateService) { // ObjectMapper 주입 추가 + super(jobRepository, transactionManager); + this.shipDetailDataProcessor = shipDetailDataProcessor; + this.shipDetailDataWriter = shipDetailDataWriter; + this.jdbcTemplate = jdbcTemplate; + this.maritimeApiWebClient = maritimeApiWebClient; + this.objectMapper = objectMapper; // ObjectMapper 초기화 + this.batchDateService = batchDateService; + } + + @Override + protected String getJobName() { + return "ShipDetailUpdateJob"; + } + + @Override + protected String getStepName() { + return "ShipDetailUpdateStep"; + } + + @Override + protected ItemReader createReader() { // 타입 변경 + // Reader 생성자 수정: ObjectMapper를 전달합니다. + return new ShipDetailUpdateDataReader(maritimeApiWebClient, jdbcTemplate, objectMapper, batchDateService); + } + + @Override + protected ItemProcessor createProcessor() { + return shipDetailDataProcessor; + } + + @Override + protected ItemWriter createWriter() { // 타입 변경 + return shipDetailDataWriter; + } + + @Override + protected int getChunkSize() { + return 30; // API에서 100개씩 가져오므로 chunk도 100으로 설정 + } + + @Bean(name = "ShipDetailUpdateJob") + public Job ShipDetailUpdateJob() { + return job(); + } + + @Bean(name = "ShipDetailUpdateStep") + public Step ShipDetailUpdateStep() { + return step(); + } +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/AdditionalInformationDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/AdditionalInformationDto.java new file mode 100644 index 0000000..2e16054 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/AdditionalInformationDto.java @@ -0,0 +1,45 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class AdditionalInformationDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("ShipEmail") + private String shipEmail; + @JsonProperty("WaterDepthMax") + private String waterDepthMax; + @JsonProperty("DrillDepthMax") + private String drillDepthMax; + @JsonProperty("DrillBargeInd") + private String drillBargeInd; + @JsonProperty("ProductionVesselInd") + private String productionVesselInd; + @JsonProperty("DeckHeatExchangerInd") + private String deckHeatExchangerInd; + @JsonProperty("DeckHeatExchangerMaterial") + private String deckHeatExchangerMaterial; + @JsonProperty("TweenDeckPortable") + private String tweenDeckPortable; + @JsonProperty("TweenDeckFixed") + private String tweenDeckFixed; + @JsonProperty("SatComID") + private String satComID; + @JsonProperty("SatComAnsBack") + private String satComAnsBack; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/BareBoatCharterHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/BareBoatCharterHistoryDto.java new file mode 100644 index 0000000..9c8449c --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/BareBoatCharterHistoryDto.java @@ -0,0 +1,30 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class BareBoatCharterHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; + @JsonProperty("EffectiveDate") + private String effectiveDate; + @JsonProperty("BBChartererCode") + private String bbChartererCode; + @JsonProperty("BBCharterer") + private String bbCharterer; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CallSignAndMmsiHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CallSignAndMmsiHistoryDto.java new file mode 100644 index 0000000..38153e7 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CallSignAndMmsiHistoryDto.java @@ -0,0 +1,32 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class CallSignAndMmsiHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("Lrno") + private String lrno; + @JsonProperty("SeqNo") + private String seqNo; + @JsonProperty("CallSign") + private String callSign; + @JsonProperty("Mmsi") + private String mmsi; + @JsonProperty("EffectiveDate") + private String effectiveDate; + // MMSI는 JSON에 없으므로 DTO에 포함하지 않음. Entity에서 처리. +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ClassHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ClassHistoryDto.java new file mode 100644 index 0000000..75a07be --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ClassHistoryDto.java @@ -0,0 +1,37 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class ClassHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("Class") + private String _class; // 'class' is a reserved keyword in Java + @JsonProperty("ClassCode") + private String classCode; + @JsonProperty("ClassIndicator") + private String classIndicator; + @JsonProperty("ClassID") + private String classID; + @JsonProperty("CurrentIndicator") + private String currentIndicator; + @JsonProperty("EffectiveDate") + private String effectiveDate; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CrewListDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CrewListDto.java new file mode 100644 index 0000000..89bf1b3 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CrewListDto.java @@ -0,0 +1,61 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class CrewListDto { + // Nested class for DataSetVersion object + @Getter + @Setter + @ToString + @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + + @JsonProperty("ID") + private String id; + + @JsonProperty("LRNO") + private String lrno; + + @JsonProperty("Shipname") + private String shipname; + + @JsonProperty("CrewListDate") + private String crewListDate; + + @JsonProperty("Nationality") + private String nationality; + + @JsonProperty("TotalCrew") + private String totalCrew; + + @JsonProperty("TotalRatings") + private String totalRatings; + + @JsonProperty("TotalOfficers") + private String totalOfficers; + + @JsonProperty("TotalCadets") + private String totalCadets; + + @JsonProperty("TotalTrainees") + private String totalTrainees; + + @JsonProperty("TotalRidingSquad") + private String totalRidingSquad; + + @JsonProperty("TotalUndeclared") + private String totalUndeclared; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/FlagHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/FlagHistoryDto.java new file mode 100644 index 0000000..ae6b5f6 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/FlagHistoryDto.java @@ -0,0 +1,31 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class FlagHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("EffectiveDate") + private String effectiveDate; + @JsonProperty("Flag") + private String flag; + @JsonProperty("FlagCode") + private String flagCode; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/GroupBeneficialOwnerHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/GroupBeneficialOwnerHistoryDto.java new file mode 100644 index 0000000..e27a2d1 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/GroupBeneficialOwnerHistoryDto.java @@ -0,0 +1,45 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class GroupBeneficialOwnerHistoryDto { + + // Nested class for DataSetVersion object + @Getter + @Setter + @ToString + @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; // 데이터셋버전 + + @JsonProperty("CompanyStatus") + private String companyStatus; // 회사상태 + + @JsonProperty("EffectiveDate") + private String effectiveDate; // 효력일 + + @JsonProperty("GroupBeneficialOwner") + private String groupBeneficialOwner; // 그룹실질소유자 + + @JsonProperty("GroupBeneficialOwnerCode") + private String groupBeneficialOwnerCode; // 그룹실질소유자코드 + + @JsonProperty("LRNO") + private String lrno; // LR/IMO번호 + + @JsonProperty("Sequence") + private String sequence; // 순번 +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/IceClassDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/IceClassDto.java new file mode 100644 index 0000000..fe26e93 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/IceClassDto.java @@ -0,0 +1,27 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class IceClassDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("IceClass") + private String iceClass; + @JsonProperty("IceClassCode") + private String iceClassCode; + @JsonProperty("LRNO") + private String lrno; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/NameHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/NameHistoryDto.java new file mode 100644 index 0000000..9b9df13 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/NameHistoryDto.java @@ -0,0 +1,29 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class NameHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("Effective_Date") + private String effectiveDate; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; + @JsonProperty("VesselName") + private String vesselName; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/OperatorHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/OperatorHistoryDto.java new file mode 100644 index 0000000..9a8188a --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/OperatorHistoryDto.java @@ -0,0 +1,43 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class OperatorHistoryDto { + + @Getter + @Setter + @ToString + @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + + @JsonProperty("CompanyStatus") + private String companyStatus; + + @JsonProperty("EffectiveDate") + private String effectiveDate; + + @JsonProperty("LRNO") + private String lrno; + + @JsonProperty("Operator") + private String operator; + + @JsonProperty("OperatorCode") + private String operatorCode; + + @JsonProperty("Sequence") + private String sequence; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/PandIHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/PandIHistoryDto.java new file mode 100644 index 0000000..b129dea --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/PandIHistoryDto.java @@ -0,0 +1,33 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class PandIHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; + @JsonProperty("PandIClubCode") + private String pandIClubCode; + @JsonProperty("PandIClubDecode") + private String pandIClubDecode; + @JsonProperty("EffectiveDate") + private String effectiveDate; + @JsonProperty("Source") + private String source; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SafetyManagementCertificateHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SafetyManagementCertificateHistoryDto.java new file mode 100644 index 0000000..9dffa4f --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SafetyManagementCertificateHistoryDto.java @@ -0,0 +1,49 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class SafetyManagementCertificateHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("SafetyManagementCertificateAuditor") + private String safetyManagementCertificateAuditor; + @JsonProperty("SafetyManagementCertificateConventionOrVol") + private String safetyManagementCertificateConventionOrVol; + @JsonProperty("SafetyManagementCertificateDateExpires") + private String safetyManagementCertificateDateExpires; + @JsonProperty("SafetyManagementCertificateDateIssued") + private String safetyManagementCertificateDateIssued; + @JsonProperty("SafetyManagementCertificateDOCCompany") + private String safetyManagementCertificateDOCCompany; + @JsonProperty("SafetyManagementCertificateFlag") + private String safetyManagementCertificateFlag; + @JsonProperty("SafetyManagementCertificateIssuer") + private String safetyManagementCertificateIssuer; + @JsonProperty("SafetyManagementCertificateOtherDescription") + private String safetyManagementCertificateOtherDescription; + @JsonProperty("SafetyManagementCertificateShipName") + private String safetyManagementCertificateShipName; + @JsonProperty("SafetyManagementCertificateShipType") + private String safetyManagementCertificateShipType; + @JsonProperty("SafetyManagementCertificateSource") + private String safetyManagementCertificateSource; + @JsonProperty("SafetyManagementCertificateCompanyCode") + private String safetyManagementCertificateCompanyCode; + @JsonProperty("Sequence") + private String sequence; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailDto.java index 3a1aa44..2682d7d 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailDto.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailDto.java @@ -183,91 +183,150 @@ public class ShipDetailDto { @JsonProperty("OwnerHistory") private List ownerHistory; + /** + * 승선자 정보 List + * API: CrewList + */ + @JsonProperty("CrewList") + private List crewList; /** - * TODO : Core20 Dto 작성 - * shipresultindex int8 NOT NULL, -- 결과인덱스 - * batch_flag varchar(1) DEFAULT 'N'::character varying NULL -- 업데이트 이력 확인 (N:대기,P:진행,S:완료) - * vesselid varchar(7) NOT NULL, -- 선박ID - * ihslrorimoshipno varchar(7) NOT NULL, -- IMO번호 - * maritimemobileserviceidentitymmsinumber varchar(9) NULL, -- MMSI - * shipname varchar(100) NULL, -- 선명 - * callsign varchar(5) NULL, -- 호출부호 - * flagname varchar(100) NULL, -- 국가 - * portofregistry varchar(50) NULL, -- 등록항 - * classificationsociety varchar(50) NULL, -- 선급 - * shiptypelevel5 varchar(15) NULL, -- 선종(Lv5) - * shiptypelevel5subtype varchar(15) NULL, -- 세부선종 - * yearofbuild varchar(4) NULL, -- 건조연도 - * shipbuilder varchar(100) NULL, -- 조선소 - * lengthoverallloa numeric(3, 3) NULL, -- 전장(LOA)[m] - * breadthmoulded numeric(3, 3) NULL, -- 형폭(몰디드)[m] - * "depth" numeric(3, 3) NULL, -- 깊이[m] - * draught numeric(3, 3) NULL, -- 흘수[m] - * grosstonnage varchar(4) NULL, -- 총톤수(GT) - * deadweight varchar(5) NULL, -- 재화중량톤수(DWT) - * teu varchar(1) NULL, -- 컨테이너(TEU) - * speedservice numeric(2, 2) NULL, -- 항속(kt) - * mainenginetype varchar(2) NULL, -- 주기관 형식 + * 화물 적재 정보 List + * API: StowageCommodity */ - // TODO : List/Array 데이터 추가 + @JsonProperty("StowageCommodity") + private List stowageCommodity; + /** - * 선박 추가 정보 List + * 그룹 실질 소유자 이력 List + * API: GroupBeneficialOwnerHistory + */ + @JsonProperty("GroupBeneficialOwnerHistory") + private List groupBeneficialOwnerHistory; + + /** + * 선박 관리자 이력 List + * API: ShipManagerHistory + */ + @JsonProperty("ShipManagerHistory") + private List shipManagerHistory; + + /** + * 운항사 이력 List + * API: OperatorHistory + */ + @JsonProperty("OperatorHistory") + private List operatorHistory; + + /** + * 기술 관리자 이력 List + * API: TechnicalManagerHistory + */ + @JsonProperty("TechnicalManagerHistory") + private List technicalManagerHistory; + + /** + * 나용선(Bare Boat Charter) 이력 List + * API: BareBoatCharterHistory + */ + @JsonProperty("BareBoatCharterHistory") + private List bareBoatCharterHistory; + + /** + * 선박 이름 이력 List + * API: NameHistory + */ + @JsonProperty("NameHistory") + private List nameHistory; + + /** + * 선박 국적/선기 이력 List + * API: FlagHistory + */ + @JsonProperty("FlagHistory") + private List flagHistory; + + /** + * 추가 정보 List (API 명세에 따라 단일 객체일 수 있으나 List로 선언) * API: AdditionalInformation */ -// @JsonProperty("AdditionalInformation") -// private List additionalInformation; - + @JsonProperty("AdditionalInformation") + private List additionalInformation; /** - * auxengine - * auxgenerator - * ballastwatermanagement - * bareboatcharterhistory - * builderaddress - * callsignandmmsihistory - * capacities - * cargopump - * classcurrent - * classhistory - * companycompliancedetails - * companydetailscomplexwithcodesa - * companyfleetcounts - * companyorderbookcounts - * companyvesselrelationships - * crewlist - * darkactivityconfirmed - * enginebuilder - * flaghistory - * grosstonnagehistory - * groupbeneficialownerhistory - * iceclass - * liftinggear - * mainengine - * namehistory - * operatorhistory - * ownerhistory - * pandihistory - * propellers - * safetymanagementcertificatehist - * sales - * scrubberdetails - * shipbuilderandsubcontractor - * shipbuilderdetail - * shipbuilderhistory - * shipcertificatesall - * shipmanagerhistory - * shiptypehistory - * sistershiplinks - * specialfeature - * statushistory - * stowagecommodity - * surveydates - * surveydateshistoryunique - * tankcoatings - * technicalmanagerhistory - * thrusters - * */ + * P&I 보험 이력 List + * API: PandIHistory + */ + @JsonProperty("PandIHistory") + private List pandIHistory; + /** + * 호출 부호 및 MMSI 이력 List + * API: CallSignAndMmsiHistory + */ + @JsonProperty("CallSignAndMmsiHistory") + private List callSignAndMmsiHistory; + /** + * 내빙 등급 List + * API: IceClass + */ + @JsonProperty("IceClass") + private List iceClass; + + /** + * 안전 관리 증서 이력 List + * API: SafetyManagementCertificateHistory + */ + @JsonProperty("SafetyManagementCertificateHistory") + private List safetyManagementCertificateHistory; + + /** + * 선급 이력 List + * API: ClassHistory + */ + @JsonProperty("ClassHistory") + private List classHistory; + + /** + * 검사 일자 이력 List (집계된 정보) + * API: SurveyDatesHistory + */ + @JsonProperty("SurveyDates") + private List surveyDatesHistory; + + /** + * 검사 일자 이력 List (개별 상세 정보) + * API: SurveyDatesHistoryUnique + */ + @JsonProperty("SurveyDatesHistoryUnique") + private List surveyDatesHistoryUnique; + + /** + * 자매선 연결 정보 List + * API: SisterShipLinks + */ + @JsonProperty("SisterShipLinks") + private List sisterShipLinks; + + /** + * 선박 상태 이력 List + * API: StatusHistory + */ + @JsonProperty("StatusHistory") + private List statusHistory; + + /** + * 특수 기능/설비 List + * API: SpecialFeature + */ + @JsonProperty("SpecialFeature") + private List specialFeature; + + /** + * 추진기 정보 List + * API: Thrusters + */ + @JsonProperty("Thrusters") + private List thrusters; } diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailUpdate.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailUpdate.java index 9878b76..f998552 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailUpdate.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDetailUpdate.java @@ -1,8 +1,6 @@ package com.snp.batch.jobs.shipdetail.batch.dto; -import com.snp.batch.jobs.shipdetail.batch.entity.OwnerHistoryEntity; -import com.snp.batch.jobs.shipdetail.batch.entity.ShipDetailEntity; -import com.snp.batch.jobs.shipdetail.batch.entity.ShipHashEntity; +import com.snp.batch.jobs.shipdetail.batch.entity.*; import lombok.Builder; import lombok.Getter; @@ -24,5 +22,25 @@ public class ShipDetailUpdate { // 이 외에 OwnerHistory Entity, Core Entity 등 증분 데이터를 추가합니다. private final ShipDetailEntity shipDetailEntity; private final List ownerHistoryEntityList; - + private final List crewListEntityList; + private final List stowageCommodityEntityList; + private final List groupBeneficialOwnerHistoryEntityList; + private final List shipManagerHistoryEntityList; + private final List operatorHistoryEntityList; + private final List technicalManagerHistoryEntityList; + private final List bareBoatCharterHistoryEntityList; + private final List nameHistoryEntityList; + private final List flagHistoryEntityList; + private final List additionalInformationEntityList; + private final List pandIHistoryEntityList; + private final List callSignAndMmsiHistoryEntityList; + private final List iceClassEntityList; + private final List safetyManagementCertificateHistoryEntityList; + private final List classHistoryEntityList; + private final List surveyDatesHistoryEntityList; + private final List surveyDatesHistoryUniqueEntityList; + private final List sisterShipLinksEntityList; + private final List statusHistoryEntityList; + private final List specialFeatureEntityList; + private final List thrustersEntityList; } \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDto.java new file mode 100644 index 0000000..ba849b2 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipDto.java @@ -0,0 +1,34 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class ShipDto { + + @JsonProperty("DataSetVersion") + private DataSetVersion dataSetVersion; + + @JsonProperty("CoreShipInd") + private String coreShipInd; + + @JsonProperty("IHSLRorIMOShipNo") + private String imoNumber; + + @Data + @NoArgsConstructor + @AllArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DataSetVersion { + @JsonProperty("DataSetVersion") + private String version; + } +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipManagerHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipManagerHistoryDto.java new file mode 100644 index 0000000..f316b20 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipManagerHistoryDto.java @@ -0,0 +1,45 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class ShipManagerHistoryDto { + + // Nested class for DataSetVersion object + @Getter + @Setter + @ToString + @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + + @JsonProperty("CompanyStatus") + private String companyStatus; + + @JsonProperty("EffectiveDate") + private String effectiveDate; + + @JsonProperty("LRNO") + private String lrno; + + @JsonProperty("Sequence") + private String sequence; + + @JsonProperty("ShipManager") + private String shipManager; + + @JsonProperty("ShipManagerCode") + private String shipManagerCode; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipUpdateApiResponse.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipUpdateApiResponse.java new file mode 100644 index 0000000..b4d70d3 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ShipUpdateApiResponse.java @@ -0,0 +1,23 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class ShipUpdateApiResponse { + + @JsonProperty("shipCount") + private Integer shipCount; + + @JsonProperty("Ships") + private List ships; + +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SisterShipLinksDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SisterShipLinksDto.java new file mode 100644 index 0000000..f651116 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SisterShipLinksDto.java @@ -0,0 +1,25 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class SisterShipLinksDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("Lrno") + private String lrno; + @JsonProperty("LinkedLRNO") + private String linkedLRNO; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SpecialFeatureDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SpecialFeatureDto.java new file mode 100644 index 0000000..ae6dd07 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SpecialFeatureDto.java @@ -0,0 +1,29 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class SpecialFeatureDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; + @JsonProperty("SpecialFeature") + private String specialFeature; + @JsonProperty("SpecialFeatureCode") + private String specialFeatureCode; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StatusHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StatusHistoryDto.java new file mode 100644 index 0000000..b969ebf --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StatusHistoryDto.java @@ -0,0 +1,31 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class StatusHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; + @JsonProperty("Status") + private String status; + @JsonProperty("StatusCode") + private String statusCode; + @JsonProperty("StatusDate") + private String statusDate; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StowageCommodityDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StowageCommodityDto.java new file mode 100644 index 0000000..1a80dc5 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/StowageCommodityDto.java @@ -0,0 +1,43 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +@Getter +@Setter +@ToString +@NoArgsConstructor +public class StowageCommodityDto { + + // Nested class for DataSetVersion object + @Getter + @Setter + @ToString + @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + + @JsonProperty("CommodityCode") + private String commodityCode; + + @JsonProperty("CommodityDecode") + private String commodityDecode; + + @JsonProperty("LRNO") + private String lrno; + + @JsonProperty("Sequence") + private String sequence; + + @JsonProperty("StowageCode") + private String stowageCode; + + @JsonProperty("StowageDecode") + private String stowageDecode; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryDto.java new file mode 100644 index 0000000..6fd2fd7 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryDto.java @@ -0,0 +1,37 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class SurveyDatesHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("AnnualSurvey") + private String annualSurvey; + @JsonProperty("ClassSociety") + private String classSociety; + @JsonProperty("ClassSocietyCode") + private String classSocietyCode; + @JsonProperty("ContinuousMachinerySurvey") + private String continuousMachinerySurvey; + @JsonProperty("DockingSurvey") + private String dockingSurvey; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("SpecialSurvey") + private String specialSurvey; + @JsonProperty("TailShaftSurvey") + private String tailShaftSurvey; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryUniqueDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryUniqueDto.java new file mode 100644 index 0000000..4072a65 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/SurveyDatesHistoryUniqueDto.java @@ -0,0 +1,31 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class SurveyDatesHistoryUniqueDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("ClassSocietyCode") + private String classSocietyCode; + @JsonProperty("SurveyDate") + private String surveyDate; + @JsonProperty("SurveyType") + private String surveyType; + @JsonProperty("ClassSociety") + private String classSociety; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/TechnicalManagerHistoryDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/TechnicalManagerHistoryDto.java new file mode 100644 index 0000000..4c87310 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/TechnicalManagerHistoryDto.java @@ -0,0 +1,32 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class TechnicalManagerHistoryDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("CompanyStatus") + private String companyStatus; + @JsonProperty("EffectiveDate") + private String effectiveDate; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; + @JsonProperty("TechnicalManager") + private String technicalManager; + @JsonProperty("TechnicalManagerCode") + private String technicalManagerCode; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ThrustersDto.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ThrustersDto.java new file mode 100644 index 0000000..6ff5830 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/ThrustersDto.java @@ -0,0 +1,39 @@ +package com.snp.batch.jobs.shipdetail.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@NoArgsConstructor +public class ThrustersDto { + @Getter @Setter @ToString @NoArgsConstructor + public static class DataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; + } + @JsonProperty("DataSetVersion") + private DataSetVersionDto dataSetVersion; + @JsonProperty("LRNO") + private String lrno; + @JsonProperty("Sequence") + private String sequence; + @JsonProperty("ThrusterType") + private String thrusterType; + @JsonProperty("ThrusterTypeCode") + private String thrusterTypeCode; + @JsonProperty("NumberOfThrusters") + private String numberOfThrusters; // numeric(20) in DB + @JsonProperty("ThrusterPosition") + private String thrusterPosition; + @JsonProperty("ThrusterBHP") + private String thrusterBHP; // numeric(20) in DB + @JsonProperty("ThrusterKW") + private String thrusterKW; // numeric(20) in DB + @JsonProperty("TypeOfInstallation") + private String typeOfInstallation; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/AdditionalInformationEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/AdditionalInformationEntity.java new file mode 100644 index 0000000..de98856 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/AdditionalInformationEntity.java @@ -0,0 +1,29 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class AdditionalInformationEntity extends BaseEntity { + // SQL Table: additionalshipsdata + private String lrno; + private String shipemail; + private String waterdepthmax; + private String drilldepthmax; + private String drillbargeind; + private String productionvesselind; + private String deckheatexchangerind; + private String deckheatexchangermaterial; + private String tweendeckportable; + private String tweendeckfixed; + private String satcomid; + private String satcomansback; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/BareBoatCharterHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/BareBoatCharterHistoryEntity.java new file mode 100644 index 0000000..0bea631 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/BareBoatCharterHistoryEntity.java @@ -0,0 +1,21 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class BareBoatCharterHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String sequence; + private String effectiveDate; + private String bbChartererCode; + private String bbCharterer; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CallSignAndMmsiHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CallSignAndMmsiHistoryEntity.java new file mode 100644 index 0000000..a8527d7 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CallSignAndMmsiHistoryEntity.java @@ -0,0 +1,22 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CallSignAndMmsiHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String sequence; // JSON: SeqNo + private String callsign; + private String mmsi; // JSON에 없음 + private String effectiveDate; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ClassHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ClassHistoryEntity.java new file mode 100644 index 0000000..ca8dfcf --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ClassHistoryEntity.java @@ -0,0 +1,25 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ClassHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String _class; // 'class' in DB + private String classCode; + private String classIndicator; + private String classID; + private String currentIndicator; + private String effectiveDate; + private String lrno; + private String sequence; // "sequence" in DB +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CrewListEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CrewListEntity.java new file mode 100644 index 0000000..86841af --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CrewListEntity.java @@ -0,0 +1,31 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CrewListEntity extends BaseEntity { + + private String dataSetVersion; // varchar(5) + private String id; // varchar(7) + private String lrno; // varchar(7) + private String shipname; // varchar(200) + private String crewlistdate; // varchar(20) + private String nationality; // varchar(200) + private String totalcrew; // varchar(2) + private String totalratings; // varchar(2) + private String totalofficers; // varchar(2) + private String totalcadets; // varchar(1) + private String totaltrainees; // varchar(1) + private String totalridingsquad; // varchar(1) + private String totalundeclared; // varchar(1) + +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/FlagHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/FlagHistoryEntity.java new file mode 100644 index 0000000..4dc1b2e --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/FlagHistoryEntity.java @@ -0,0 +1,22 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class FlagHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String effectiveDate; + private String flag; + private String flagCode; + private String lrno; + private String sequence; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/GroupBeneficialOwnerHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/GroupBeneficialOwnerHistoryEntity.java new file mode 100644 index 0000000..80e0b06 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/GroupBeneficialOwnerHistoryEntity.java @@ -0,0 +1,28 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +// BaseEntity는 프로젝트에 정의되어 있어야 합니다. +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class GroupBeneficialOwnerHistoryEntity extends BaseEntity { + + // JSON & DDL 컬럼 + private String dataSetVersion; + private String companyStatus; + private String effectiveDate; + private String groupBeneficialOwner; + private String groupBeneficialOwnerCode; + private String lrno; + private String sequence; + + // DB 관리 컬럼 (shipresultindex, vesselid, rowindex)는 Entity에서 제거됨 +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/IceClassEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/IceClassEntity.java new file mode 100644 index 0000000..a019a9a --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/IceClassEntity.java @@ -0,0 +1,20 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class IceClassEntity extends BaseEntity { + private String dataSetVersion; + private String iceClass; + private String iceClassCode; + private String lrno; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/NameHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/NameHistoryEntity.java new file mode 100644 index 0000000..2b974c6 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/NameHistoryEntity.java @@ -0,0 +1,21 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class NameHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String effectiveDate; + private String lrno; + private String sequence; + private String vesselName; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/OperatorHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/OperatorHistoryEntity.java new file mode 100644 index 0000000..c5ddf83 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/OperatorHistoryEntity.java @@ -0,0 +1,23 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class OperatorHistoryEntity extends BaseEntity { + + private String dataSetVersion; + private String companyStatus; + private String effectiveDate; + private String lrno; + private String operator; + private String operatorCode; + private String sequence; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/PandIHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/PandIHistoryEntity.java new file mode 100644 index 0000000..dfcc3d1 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/PandIHistoryEntity.java @@ -0,0 +1,23 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class PandIHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String sequence; + private String pandiclubcode; + private String pandiclubdecode; + private String effectiveDate; + private String source; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SafetyManagementCertificateHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SafetyManagementCertificateHistoryEntity.java new file mode 100644 index 0000000..8ec438f --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SafetyManagementCertificateHistoryEntity.java @@ -0,0 +1,31 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SafetyManagementCertificateHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String safetyManagementCertificateAuditor; + private String safetyManagementCertificateConventionOrVol; + private String safetyManagementCertificateDateExpires; + private String safetyManagementCertificateDateIssued; + private String safetyManagementCertificateDOCCompany; + private String safetyManagementCertificateFlag; + private String safetyManagementCertificateIssuer; + private String safetyManagementCertificateOtherDescription; + private String safetyManagementCertificateShipName; + private String safetyManagementCertificateShipType; + private String safetyManagementCertificateSource; + private String safetyManagementCertificateCompanyCode; + private String sequence; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipManagerHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipManagerHistoryEntity.java new file mode 100644 index 0000000..2fc6c07 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipManagerHistoryEntity.java @@ -0,0 +1,28 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +// BaseEntity는 프로젝트에 정의되어 있다고 가정합니다. +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ShipManagerHistoryEntity extends BaseEntity { + + // JSON & DDL 컬럼 + private String dataSetVersion; + private String companyStatus; + private String effectiveDate; // DDL: bpchar(8) 또는 varchar(8) + private String lrno; + private String sequence; + private String shipManager; + private String shipManagerCode; + + // DB 관리 컬럼 (shipresultindex, vesselid, rowindex)는 Entity에서 제거됨 +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SisterShipLinksEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SisterShipLinksEntity.java new file mode 100644 index 0000000..dd7a084 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SisterShipLinksEntity.java @@ -0,0 +1,19 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SisterShipLinksEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String linkedLRNO; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SpecialFeatureEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SpecialFeatureEntity.java new file mode 100644 index 0000000..da57585 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SpecialFeatureEntity.java @@ -0,0 +1,21 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SpecialFeatureEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String sequence; + private String specialFeature; + private String specialFeatureCode; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StatusHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StatusHistoryEntity.java new file mode 100644 index 0000000..48deefb --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StatusHistoryEntity.java @@ -0,0 +1,22 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class StatusHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String sequence; + private String status; + private String statusCode; + private String statusDate; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StowageCommodityEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StowageCommodityEntity.java new file mode 100644 index 0000000..510c270 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/StowageCommodityEntity.java @@ -0,0 +1,28 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +// BaseEntity는 프로젝트에 정의되어 있어야 합니다. +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class StowageCommodityEntity extends BaseEntity { + + // JSON & DDL 컬럼 + private String dataSetVersion; // varchar(5) + private String commodityCode; // varchar(5) + private String commodityDecode; // varchar(50) + private String lrno; // varchar(7) + private String sequence; // varchar(2) + private String stowageCode; // varchar(2) + private String stowageDecode; // varchar(50) + + // DB 관리 컬럼 (shipresultindex, vesselid, rowindex)는 Entity에서 제거됨 +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryEntity.java new file mode 100644 index 0000000..254d128 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryEntity.java @@ -0,0 +1,25 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SurveyDatesHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String annualSurvey; + private String classSociety; + private String classSocietyCode; + private String continuousMachinerySurvey; + private String dockingSurvey; + private String lrno; + private String specialSurvey; + private String tailShaftSurvey; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryUniqueEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryUniqueEntity.java new file mode 100644 index 0000000..47f1ebc --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/SurveyDatesHistoryUniqueEntity.java @@ -0,0 +1,22 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class SurveyDatesHistoryUniqueEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String classSocietyCode; + private String surveyDate; + private String surveyType; + private String classSociety; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/TechnicalManagerHistoryEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/TechnicalManagerHistoryEntity.java new file mode 100644 index 0000000..996c761 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/TechnicalManagerHistoryEntity.java @@ -0,0 +1,22 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class TechnicalManagerHistoryEntity extends BaseEntity { + private String dataSetVersion; + private String companyStatus; + private String effectiveDate; + private String lrno; + private String sequence; + private String technicalManager; + private String technicalManagerCode; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ThrustersEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ThrustersEntity.java new file mode 100644 index 0000000..9067d73 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ThrustersEntity.java @@ -0,0 +1,26 @@ +package com.snp.batch.jobs.shipdetail.batch.entity; + +import com.snp.batch.common.batch.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ThrustersEntity extends BaseEntity { + private String dataSetVersion; + private String lrno; + private String sequence; + private String thrusterType; + private String thrusterTypeCode; + private String numberOfThrusters; + private String thrusterPosition; + private String thrusterBHP; + private String thrusterKW; + private String typeOfInstallation; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/processor/ShipDetailDataProcessor.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/processor/ShipDetailDataProcessor.java index 1be7f93..5756ff4 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/processor/ShipDetailDataProcessor.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/processor/ShipDetailDataProcessor.java @@ -1,13 +1,8 @@ package com.snp.batch.jobs.shipdetail.batch.processor; import com.snp.batch.common.batch.processor.BaseProcessor; -import com.snp.batch.jobs.shipdetail.batch.dto.OwnerHistoryDto; -import com.snp.batch.jobs.shipdetail.batch.dto.ShipDetailComparisonData; -import com.snp.batch.jobs.shipdetail.batch.dto.ShipDetailDto; -import com.snp.batch.jobs.shipdetail.batch.dto.ShipDetailUpdate; -import com.snp.batch.jobs.shipdetail.batch.entity.OwnerHistoryEntity; -import com.snp.batch.jobs.shipdetail.batch.entity.ShipDetailEntity; -import com.snp.batch.jobs.shipdetail.batch.entity.ShipHashEntity; +import com.snp.batch.jobs.shipdetail.batch.dto.*; +import com.snp.batch.jobs.shipdetail.batch.entity.*; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -33,7 +28,7 @@ public class ShipDetailDataProcessor extends BaseProcessor ownerHistoryEntityList = makeOwnerHistoryEntityList(comparisonData.getStructuredDto().getOwnerHistory()); - - - - log.info("선박 데이터: shipDetailEntity={}", shipDetailEntity.toString()); - log.info("선박 데이터: ownerHistoryEntityList={}", ownerHistoryEntityList.toString()); + List crewListEntityList = makeCrewListEntityList(comparisonData.getStructuredDto().getCrewList()); + List stowageCommodityEntityList = makeStowageCommodityEntityList(comparisonData.getStructuredDto().getStowageCommodity()); + List groupBeneficialOwnerHistoryEntityList = makeGroupBeneficialOwnerHistoryEntityList(comparisonData.getStructuredDto().getGroupBeneficialOwnerHistory()); + List shipManagerHistoryEntityList = makeShipManagerHistoryEntityList(comparisonData.getStructuredDto().getShipManagerHistory()); + List operatorHistoryEntityList = makeOperatorHistoryEntityList(comparisonData.getStructuredDto().getOperatorHistory()); + List technicalManagerHistoryEntityList = makeTechnicalManagerHistoryEntityList(comparisonData.getStructuredDto().getTechnicalManagerHistory()); + List bareBoatCharterHistoryEntityList = makeBareBoatCharterHistoryEntityList(comparisonData.getStructuredDto().getBareBoatCharterHistory()); + List nameHistoryEntityList = makeNameHistoryEntityList(comparisonData.getStructuredDto().getNameHistory()); + List flagHistoryEntityList = makeFlagHistoryEntityList(comparisonData.getStructuredDto().getFlagHistory()); + List additionalInformationEntityList = makeAdditionalInformationEntityList(comparisonData.getStructuredDto().getAdditionalInformation()); + List pandIHistoryEntityList = makePandIHistoryEntityList(comparisonData.getStructuredDto().getPandIHistory()); + List callSignAndMmsiHistoryEntityList = makeCallSignAndMmsiHistoryEntityList(comparisonData.getStructuredDto().getCallSignAndMmsiHistory()); + List iceClassEntityList = makeIceClassEntityList(comparisonData.getStructuredDto().getIceClass()); + List safetyManagementCertificateHistoryEntityList = makeSafetyManagementCertificateHistoryEntityList(comparisonData.getStructuredDto().getSafetyManagementCertificateHistory()); + List classHistoryEntityList = makeClassHistoryEntityList(comparisonData.getStructuredDto().getClassHistory()); + List surveyDatesHistoryEntityList = makeSurveyDatesHistoryEntityList(comparisonData.getStructuredDto().getSurveyDatesHistory()); + List surveyDatesHistoryUniqueEntityList = makeSurveyDatesHistoryUniqueEntityList(comparisonData.getStructuredDto().getSurveyDatesHistoryUnique()); + List sisterShipLinksEntityList = makeSisterShipLinksEntityList(comparisonData.getStructuredDto().getSisterShipLinks()); + List statusHistoryEntityList = makeStatusHistoryEntityList(comparisonData.getStructuredDto().getStatusHistory()); + List specialFeatureEntityList = makeSpecialFeatureEntityList(comparisonData.getStructuredDto().getSpecialFeature()); + List thrustersEntityList = makeThrustersEntityList(comparisonData.getStructuredDto().getThrusters()); // 3. 최종 업데이트 DTO 생성 (Writer에 전달) return ShipDetailUpdate.builder() @@ -57,6 +67,27 @@ public class ShipDetailDataProcessor extends BaseProcessor makeCrewListEntityList(List dtoList){ + List crewListEntityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return crewListEntityList; // 빈 리스트 또는 null 입력 시 빈 리스트 반환 + } + + for (CrewListDto dto : dtoList) { + String datasetVersion = null; + if (dto.getDataSetVersion() != null) { + datasetVersion = dto.getDataSetVersion().getDataSetVersion(); + } + + // superBuilder() 대신 builder() 사용 + CrewListEntity entity = CrewListEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .id(safeGetString(dto.getId())) + .lrno(safeGetString(dto.getLrno())) + .shipname(safeGetString(dto.getShipname())) + .crewlistdate(safeGetString(dto.getCrewListDate())) + .nationality(safeGetString(dto.getNationality())) + .totalcrew(safeGetString(dto.getTotalCrew())) + .totalratings(safeGetString(dto.getTotalRatings())) + .totalofficers(safeGetString(dto.getTotalOfficers())) + .totalcadets(safeGetString(dto.getTotalCadets())) + .totaltrainees(safeGetString(dto.getTotalTrainees())) + .totalridingsquad(safeGetString(dto.getTotalRidingSquad())) + .totalundeclared(safeGetString(dto.getTotalUndeclared())) + // DB에서 관리되는 컬럼 임시 셋팅 로직 제거 + .build(); + + crewListEntityList.add(entity); + } + + return crewListEntityList; + } + + private List makeStowageCommodityEntityList(List dtoList){ + List entityList = new ArrayList<>(); + + if (dtoList == null || dtoList.isEmpty()) { + return entityList; // 빈 리스트 또는 null 입력 시 빈 리스트 반환 + } + + for (StowageCommodityDto dto : dtoList) { + String datasetVersion = null; + if (dto.getDataSetVersion() != null) { + datasetVersion = dto.getDataSetVersion().getDataSetVersion(); + } + + // builder() 사용 및 DB 관리 컬럼 셋팅 로직 제거 + StowageCommodityEntity entity = StowageCommodityEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .commodityCode(safeGetString(dto.getCommodityCode())) + .commodityDecode(safeGetString(dto.getCommodityDecode())) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .stowageCode(safeGetString(dto.getStowageCode())) + .stowageDecode(safeGetString(dto.getStowageDecode())) + .build(); + + entityList.add(entity); + } + + return entityList; + } + + private List makeGroupBeneficialOwnerHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + + if (dtoList == null || dtoList.isEmpty()) { + return entityList; // 빈 리스트 또는 null 입력 시 빈 리스트 반환 + } + + for (GroupBeneficialOwnerHistoryDto dto : dtoList) { + String datasetVersion = null; + if (dto.getDataSetVersion() != null) { + datasetVersion = dto.getDataSetVersion().getDataSetVersion(); + } + + // builder() 사용 및 DB 관리 컬럼 셋팅 로직 제거 + GroupBeneficialOwnerHistoryEntity entity = GroupBeneficialOwnerHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .companyStatus(safeGetString(dto.getCompanyStatus())) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .groupBeneficialOwner(safeGetString(dto.getGroupBeneficialOwner())) + .groupBeneficialOwnerCode(safeGetString(dto.getGroupBeneficialOwnerCode())) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .build(); + + entityList.add(entity); + } + + return entityList; + } + + private List makeShipManagerHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + + if (dtoList == null || dtoList.isEmpty()) { + return entityList; // 빈 리스트 또는 null 입력 시 빈 리스트 반환 + } + + for (ShipManagerHistoryDto dto : dtoList) { + String datasetVersion = null; + if (dto.getDataSetVersion() != null) { + datasetVersion = dto.getDataSetVersion().getDataSetVersion(); + } + + // builder() 사용 및 DB 관리 컬럼 셋팅 로직 제거 + ShipManagerHistoryEntity entity = ShipManagerHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .companyStatus(safeGetString(dto.getCompanyStatus())) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .shipManager(safeGetString(dto.getShipManager())) + .shipManagerCode(safeGetString(dto.getShipManagerCode())) + .build(); + + entityList.add(entity); + } + + return entityList; + } + + private List makeOperatorHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + + for (OperatorHistoryDto dto : dtoList) { + String datasetVersion = null; + if (dto.getDataSetVersion() != null) { + datasetVersion = dto.getDataSetVersion().getDataSetVersion(); + } + + // builder() 사용 및 DB 관리 컬럼 셋팅 로직 제거 + OperatorHistoryEntity entity = OperatorHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .companyStatus(safeGetString(dto.getCompanyStatus())) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .lrno(safeGetString(dto.getLrno())) + .operator(safeGetString(dto.getOperator())) + .operatorCode(safeGetString(dto.getOperatorCode())) + .sequence(safeGetString(dto.getSequence())) + .build(); + + entityList.add(entity); + } + + return entityList; + } + + private List makeTechnicalManagerHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (TechnicalManagerHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + TechnicalManagerHistoryEntity entity = TechnicalManagerHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .companyStatus(safeGetString(dto.getCompanyStatus())) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .technicalManager(safeGetString(dto.getTechnicalManager())) + .technicalManagerCode(safeGetString(dto.getTechnicalManagerCode())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeBareBoatCharterHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (BareBoatCharterHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + BareBoatCharterHistoryEntity entity = BareBoatCharterHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .bbChartererCode(safeGetString(dto.getBbChartererCode())) + .bbCharterer(safeGetString(dto.getBbCharterer())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeNameHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (NameHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + NameHistoryEntity entity = NameHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .vesselName(safeGetString(dto.getVesselName())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeFlagHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (FlagHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + FlagHistoryEntity entity = FlagHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .flag(safeGetString(dto.getFlag())) + .flagCode(safeGetString(dto.getFlagCode())) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeAdditionalInformationEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (AdditionalInformationDto dto : dtoList) { + // DataSetVersion은 DB 스키마에 없으므로 Entity에 매핑하지 않음 + AdditionalInformationEntity entity = AdditionalInformationEntity.builder() + .lrno(safeGetString(dto.getLrno())) + .shipemail(safeGetString(dto.getShipEmail())) + .waterdepthmax(safeGetString(dto.getWaterDepthMax())) + .drilldepthmax(safeGetString(dto.getDrillDepthMax())) + .drillbargeind(safeGetString(dto.getDrillBargeInd())) + .productionvesselind(safeGetString(dto.getProductionVesselInd())) + .deckheatexchangerind(safeGetString(dto.getDeckHeatExchangerInd())) + .deckheatexchangermaterial(safeGetString(dto.getDeckHeatExchangerMaterial())) + .tweendeckportable(safeGetString(dto.getTweenDeckPortable())) + .tweendeckfixed(safeGetString(dto.getTweenDeckFixed())) + .satcomid(safeGetString(dto.getSatComID())) + .satcomansback(safeGetString(dto.getSatComAnsBack())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makePandIHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (PandIHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + PandIHistoryEntity entity = PandIHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .pandiclubcode(safeGetString(dto.getPandIClubCode())) + .pandiclubdecode(safeGetString(dto.getPandIClubDecode())) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .source(safeGetString(dto.getSource())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeCallSignAndMmsiHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (CallSignAndMmsiHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + CallSignAndMmsiHistoryEntity entity = CallSignAndMmsiHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSeqNo())) // SeqNo -> sequence + .callsign(safeGetString(dto.getCallSign())) + .mmsi(dto.getMmsi()) // JSON에 MMSI 필드가 없으므로 null 또는 기본값 (필요 시 수정) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeIceClassEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (IceClassDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + IceClassEntity entity = IceClassEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .iceClass(safeGetString(dto.getIceClass())) + .iceClassCode(safeGetString(dto.getIceClassCode())) + .lrno(safeGetString(dto.getLrno())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeSafetyManagementCertificateHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (SafetyManagementCertificateHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + SafetyManagementCertificateHistoryEntity entity = SafetyManagementCertificateHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .safetyManagementCertificateAuditor(safeGetString(dto.getSafetyManagementCertificateAuditor())) + .safetyManagementCertificateConventionOrVol(safeGetString(dto.getSafetyManagementCertificateConventionOrVol())) + .safetyManagementCertificateDateExpires(safeGetString(dto.getSafetyManagementCertificateDateExpires())) + .safetyManagementCertificateDateIssued(safeGetString(dto.getSafetyManagementCertificateDateIssued())) + .safetyManagementCertificateDOCCompany(safeGetString(dto.getSafetyManagementCertificateDOCCompany())) + .safetyManagementCertificateFlag(safeGetString(dto.getSafetyManagementCertificateFlag())) + .safetyManagementCertificateIssuer(safeGetString(dto.getSafetyManagementCertificateIssuer())) + .safetyManagementCertificateOtherDescription(safeGetString(dto.getSafetyManagementCertificateOtherDescription())) + .safetyManagementCertificateShipName(safeGetString(dto.getSafetyManagementCertificateShipName())) + .safetyManagementCertificateShipType(safeGetString(dto.getSafetyManagementCertificateShipType())) + .safetyManagementCertificateSource(safeGetString(dto.getSafetyManagementCertificateSource())) + .safetyManagementCertificateCompanyCode(safeGetString(dto.getSafetyManagementCertificateCompanyCode())) + .sequence(safeGetString(dto.getSequence())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeClassHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (ClassHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + ClassHistoryEntity entity = ClassHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + ._class(safeGetString(dto.get_class())) + .classCode(safeGetString(dto.getClassCode())) + .classIndicator(safeGetString(dto.getClassIndicator())) + .classID(safeGetString(dto.getClassID())) + .currentIndicator(safeGetString(dto.getCurrentIndicator())) + .effectiveDate(safeGetString(dto.getEffectiveDate())) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeSurveyDatesHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (SurveyDatesHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + SurveyDatesHistoryEntity entity = SurveyDatesHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .annualSurvey(safeGetString(dto.getAnnualSurvey())) + .classSociety(safeGetString(dto.getClassSociety())) + .classSocietyCode(safeGetString(dto.getClassSocietyCode())) + .continuousMachinerySurvey(safeGetString(dto.getContinuousMachinerySurvey())) + .dockingSurvey(safeGetString(dto.getDockingSurvey())) + .lrno(safeGetString(dto.getLrno())) + .specialSurvey(safeGetString(dto.getSpecialSurvey())) + .tailShaftSurvey(safeGetString(dto.getTailShaftSurvey())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeSurveyDatesHistoryUniqueEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (SurveyDatesHistoryUniqueDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + SurveyDatesHistoryUniqueEntity entity = SurveyDatesHistoryUniqueEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .classSocietyCode(safeGetString(dto.getClassSocietyCode())) + .surveyDate(safeGetString(dto.getSurveyDate())) + .surveyType(safeGetString(dto.getSurveyType())) + .classSociety(safeGetString(dto.getClassSociety())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeSisterShipLinksEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (SisterShipLinksDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + SisterShipLinksEntity entity = SisterShipLinksEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .linkedLRNO(safeGetString(dto.getLinkedLRNO())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeStatusHistoryEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (StatusHistoryDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + StatusHistoryEntity entity = StatusHistoryEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .status(safeGetString(dto.getStatus())) + .statusCode(safeGetString(dto.getStatusCode())) + .statusDate(safeGetString(dto.getStatusDate())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeSpecialFeatureEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (SpecialFeatureDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + SpecialFeatureEntity entity = SpecialFeatureEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .specialFeature(safeGetString(dto.getSpecialFeature())) + .specialFeatureCode(safeGetString(dto.getSpecialFeatureCode())) + .build(); + entityList.add(entity); + } + return entityList; + } + + private List makeThrustersEntityList(List dtoList){ + List entityList = new ArrayList<>(); + if (dtoList == null || dtoList.isEmpty()) { + return entityList; + } + for (ThrustersDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + ThrustersEntity entity = ThrustersEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .lrno(safeGetString(dto.getLrno())) + .sequence(safeGetString(dto.getSequence())) + .thrusterType(safeGetString(dto.getThrusterType())) + .thrusterTypeCode(safeGetString(dto.getThrusterTypeCode())) + .numberOfThrusters(safeGetString(dto.getNumberOfThrusters())) + .thrusterPosition(safeGetString(dto.getThrusterPosition())) + .thrusterBHP(safeGetString(dto.getThrusterBHP())) + .thrusterKW(safeGetString(dto.getThrusterKW())) + .typeOfInstallation(safeGetString(dto.getTypeOfInstallation())) + .build(); + entityList.add(entity); + } + return entityList; + } /** * 해시값을 비교하여 변경 여부를 판단합니다. diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/reader/ShipDetailUpdateDataReader.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/reader/ShipDetailUpdateDataReader.java new file mode 100644 index 0000000..6465807 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/reader/ShipDetailUpdateDataReader.java @@ -0,0 +1,309 @@ +package com.snp.batch.jobs.shipdetail.batch.reader; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.snp.batch.common.batch.reader.BaseApiReader; +import com.snp.batch.common.util.JsonChangeDetector; +import com.snp.batch.jobs.shipdetail.batch.dto.*; +import com.snp.batch.service.BatchDateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.reactive.function.client.WebClient; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 선박 상세 정보 Reader (v2.0 - Chunk 기반) + * + * 기능: + * 1. ship_data 테이블에서 IMO 번호 전체 조회 (최초 1회) + * 2. IMO 번호를 100개씩 분할하여 배치 단위로 처리 + * 3. fetchNextBatch() 호출 시마다 100개씩 API 호출 + * 4. Spring Batch가 100건씩 Process → Write 수행 + * + * Chunk 처리 흐름: + * - beforeFetch() → IMO 전체 조회 (1회) + * - fetchNextBatch() → 100개 IMO로 API 호출 (1,718회) + * - read() → 1건씩 반환 (100번) + * - Processor/Writer → 100건 처리 + * - 반복... (1,718번의 Chunk) + * + * 기존 방식과의 차이: + * - 기존: 17만건 전체 메모리 로드 → Process → Write + * - 신규: 100건씩 로드 → Process → Write (Chunk 1,718회) + */ +@Slf4j +public class ShipDetailUpdateDataReader extends BaseApiReader { + + private final JdbcTemplate jdbcTemplate; + private final ObjectMapper objectMapper; + private final BatchDateService batchDateService; // ✨ BatchDateService 필드 추가 + + // 배치 처리 상태 + private List allImoNumbers; + // DB 해시값을 저장할 맵 + private Map dbMasterHashes; + private int currentBatchIndex = 0; + private final int batchSize = 30; + + public ShipDetailUpdateDataReader(WebClient webClient, JdbcTemplate jdbcTemplate, ObjectMapper objectMapper,BatchDateService batchDateService) { + super(webClient); + this.jdbcTemplate = jdbcTemplate; + this.objectMapper = objectMapper; + this.batchDateService = batchDateService; + enableChunkMode(); // ✨ Chunk 모드 활성화 + } + + @Override + protected String getReaderName() { + return "ShipDetailUpdateDataReader"; + } + + @Override + protected void resetCustomState() { + this.currentBatchIndex = 0; + this.allImoNumbers = null; + this.dbMasterHashes = null; + } + + @Override + protected String getApiPath() { + return "/MaritimeWCF/APSShipService.svc/RESTFul/GetShipsByIHSLRorIMONumbersAll"; + } + + protected String getShipUpdateApiPath(){ + return "MaritimeWCF/APSShipService.svc/RESTFul/GetShipChangesByLastUpdateDateRange"; + } + + private static final String FETCH_ALL_HASHES_QUERY = + "SELECT imo_number, ship_detail_hash FROM snp_data.ship_detail_hash_json ORDER BY imo_number"; + + /** + * 최초 1회만 실행: ship_data 테이블에서 IMO 번호 전체 조회 + */ + @Override + protected void beforeFetch() { + // 전처리 과정 + + // 💡 Step 1. 기간 내 변경된 IMO 번호 리스트 조회 + log.info("[{}] 변경된 IMO 번호 조회 시작...", getReaderName()); + ShipUpdateApiResponse response = callShipUpdateApi(); + allImoNumbers = extractUpdateImoNumbers(response); + log.info("[{}] 변경된 IMO 번호 수: {} 개", getReaderName(), response.getShipCount()); + + int totalBatches = (int) Math.ceil((double) allImoNumbers.size() / batchSize); + + log.info("[{}] 총 {} 개의 변경된 IMO 번호 조회 완료", getReaderName(), allImoNumbers.size()); + log.info("[{}] {}개씩 배치로 분할하여 API 호출 예정", getReaderName(), batchSize); + log.info("[{}] 예상 배치 수: {} 개", getReaderName(), totalBatches); + + // Step 2. 전 배치 결과 imo_number, ship_detail_json, ship_detail_hash 데이터 전체 조회 + log.info("[{}] DB Master Hash 전체 조회 시작...", getReaderName()); + // 1-1. DB에서 모든 IMO와 Hash 조회 + dbMasterHashes = jdbcTemplate.query(FETCH_ALL_HASHES_QUERY, rs -> { + Map map = new HashMap<>(); + while (rs.next()) { + map.put(rs.getString("imo_number"), rs.getString("ship_detail_hash")); + } + return map; + }); + + log.info("[{}] DB Master Hash 조회 완료. 총 {}건.", getReaderName(), dbMasterHashes.size()); + + // API 통계 초기화 + updateApiCallStats(totalBatches, 0); + } + + /** + * ✨ Chunk 기반 핵심 메서드: 다음 100개 배치를 조회하여 반환 + * + * Spring Batch가 100건씩 read() 호출 완료 후 이 메서드 재호출 + * + * @return 다음 배치 100건 (더 이상 없으면 null) + */ + @Override + protected List fetchNextBatch() throws Exception { + + // 모든 배치 처리 완료 확인 + if (allImoNumbers == null || currentBatchIndex >= allImoNumbers.size()) { + return null; // Job 종료 + } + + // 현재 배치의 시작/끝 인덱스 계산 + int startIndex = currentBatchIndex; + int endIndex = Math.min(currentBatchIndex + batchSize, allImoNumbers.size()); + + // 현재 배치의 IMO 번호 추출 (100개) + List currentBatch = allImoNumbers.subList(startIndex, endIndex); + + int currentBatchNumber = (currentBatchIndex / batchSize) + 1; + int totalBatches = (int) Math.ceil((double) allImoNumbers.size() / batchSize); + + log.info("[{}] 배치 {}/{} 처리 중 (IMO {} 개)...", + getReaderName(), currentBatchNumber, totalBatches, currentBatch.size()); + + try { + // IMO 번호를 쉼표로 연결 (예: "1000019,1000021,1000033,...") + String imoParam = String.join(",", currentBatch); + + // API 호출 + ShipDetailApiResponse response = callApiWithBatch(imoParam); + + // 다음 배치로 인덱스 이동 + currentBatchIndex = endIndex; + + List comparisonList = new ArrayList<>(); + + // 응답 처리 + if (response != null && response.getShipResult() != null) { + // ✨ ShipDetailComparisonData 생성 + for (ShipResultDto shipResult : response.getShipResult()) { + JsonNode rawShipDetailNode = shipResult.getShipDetailNode(); + + if (rawShipDetailNode == null) continue; + + // 💡 1) DTO로 매핑하여 IMO와 구조화된 DTO 확보 + ShipDetailDto structuredDto = null; + try { + structuredDto = objectMapper.treeToValue(rawShipDetailNode, ShipDetailDto.class); + } catch (Exception e) { + log.error("ShipDetailDto 매핑 실패: {}", e.getMessage()); + continue; + } + + String imo = structuredDto.getIhslrorimoshipno(); + if (imo == null || imo.isEmpty()) continue; + + // 💡 2) 원본 JSON 문자열 생성 + String originalJsonString = rawShipDetailNode.toString(); + + // 💡 3) API response json을 Map 형태로 변환, 정렬 및 해시 생성 + Map currentMasterMap = JsonChangeDetector.jsonToSortedFilteredMap(originalJsonString); + String currentMasterHash = JsonChangeDetector.getSha256HashFromMap(currentMasterMap); + + // 💡 4) DB Master Hash 조회 (beforeFetch에서 로드된 맵 사용) + String previousMasterHash = dbMasterHashes.getOrDefault(imo, null); + + // 💡 5) ShipDetailComparisonData DTO 생성 + comparisonList.add(ShipDetailComparisonData.builder() + .imoNumber(imo) + .previousMasterHash(previousMasterHash) + .currentMasterMap(currentMasterMap) + .currentMasterHash(currentMasterHash) + .structuredDto(structuredDto) + .build()); + } + + log.info("[{}] 배치 {}/{} 완료: {} 건 조회", + getReaderName(), currentBatchNumber, totalBatches, comparisonList.size()); + + // API 호출 통계 업데이트 + updateApiCallStats(totalBatches, currentBatchNumber); + + // API 과부하 방지 (다음 배치 전 0.5초 대기) + if (currentBatchIndex < allImoNumbers.size()) { + Thread.sleep(500); + } + + return comparisonList; + + } else { + log.warn("[{}] 배치 {}/{} 응답 없음", + getReaderName(), currentBatchNumber, totalBatches); + + // API 호출 통계 업데이트 (실패도 카운트) + updateApiCallStats(totalBatches, currentBatchNumber); + + return Collections.emptyList(); + } + + } catch (Exception e) { + log.error("[{}] 배치 {}/{} 처리 중 오류: {}", + getReaderName(), currentBatchNumber, totalBatches, e.getMessage(), e); + + // 오류 발생 시에도 다음 배치로 이동 (부분 실패 허용) + currentBatchIndex = endIndex; + + // 빈 리스트 반환 (Job 계속 진행) + return Collections.emptyList(); + } + } + + /** + * Query Parameter를 사용한 API 호출 + * + * @param imoNumbers 쉼표로 연결된 IMO 번호 (예: "1000019,1000021,...") + * @return API 응답 + */ + private ShipDetailApiResponse callApiWithBatch(String imoNumbers) { + String url = getApiPath() + "?IMONumbers=" + imoNumbers; + + log.info("[{}] API 호출: {}", getReaderName(), url); + + return webClient.get() + .uri(url) + .retrieve() + .bodyToMono(ShipDetailApiResponse.class) + .block(); + } + + private ShipUpdateApiResponse callShipUpdateApi(){ + // 1. BatchDateService를 통해 동적 날짜 파라미터 맵 조회 + Map params = batchDateService.getShipUpdateApiDateParams(); + + String url = getShipUpdateApiPath(); + log.info("[{}] API 호출: {}", getReaderName(), url); + + return webClient.get() + .uri(url, uriBuilder -> uriBuilder + // 맵에서 파라미터 값을 동적으로 가져와 세팅 + .queryParam("shipsCategory", params.get("shipsCategory")) + .queryParam("fromYear", params.get("fromYear")) + .queryParam("fromMonth", params.get("fromMonth")) + .queryParam("fromDay", params.get("fromDay")) + .queryParam("toYear", params.get("toYear")) + .queryParam("toMonth", params.get("toMonth")) + .queryParam("toDay", params.get("toDay")) + .build()) + .retrieve() + .bodyToMono(ShipUpdateApiResponse.class) + .block(); + } + + private List extractUpdateImoNumbers(ShipUpdateApiResponse response) { + if (response.getShips() == null) { + return Collections.emptyList(); + } + return response.getShips() .stream() + // ShipDto 객체에서 imoNumber 필드 (String 타입)를 추출 + .map(ShipDto::getImoNumber) + // IMO 번호가 null이 아닌 경우만 필터링 (선택 사항이지만 안전성을 위해) + .filter(imoNumber -> imoNumber != null) + // 추출된 String imoNumber들을 List으로 수집 + .collect(Collectors.toList()); + } + + @Override + protected void afterFetch(List data) { + int totalBatches = (int) Math.ceil((double) allImoNumbers.size() / batchSize); + try{ + if (data == null) { + // 3. ✨ 배치 성공 시 상태 업데이트 (트랜잭션 커밋 직전에 실행) + LocalDate successDate = LocalDate.now(); // 현재 배치 실행 시점의 날짜 (Reader의 toDay와 동일한 값) + batchDateService.updateLastSuccessDate(successDate); + log.info("batch_last_execution update 완료"); + + log.info("[{}] 전체 {} 개 배치 처리 완료", getReaderName(), totalBatches); + log.info("[{}] 총 {} 개의 IMO 번호에 대한 API 호출 종료", + getReaderName(), allImoNumbers.size()); + } + }catch (Exception e){ + log.info("[{}] 전체 {} 개 배치 처리 실패", getReaderName(), totalBatches); + log.info("[{}] 총 {} 개의 IMO 번호에 대한 API 호출 종료", + getReaderName(), allImoNumbers.size()); + } + } + +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepository.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepository.java index 9dd59e3..e192014 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepository.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepository.java @@ -1,7 +1,6 @@ package com.snp.batch.jobs.shipdetail.batch.repository; -import com.snp.batch.jobs.shipdetail.batch.entity.OwnerHistoryEntity; -import com.snp.batch.jobs.shipdetail.batch.entity.ShipDetailEntity; +import com.snp.batch.jobs.shipdetail.batch.entity.*; import java.util.List; @@ -16,6 +15,69 @@ public interface ShipDetailRepository { void saveAllOwnerHistoryData(List entities); + //TODO : CrewList 저장 함수 선언 + void saveAllCrewListData(List entities); + + //TODO : StowageCommodity 저장 함수 선언 + void saveAllStowageCommodityData(List entities); + + //TODO : GroupBeneficialOwnerHistory 저장 함수 선언 + void saveAllGroupBeneficialOwnerHistoryData(List entities); + + //TODO : ShipManagerHistory 저장 함수 선언 + void saveAllShipManagerHistoryData(List entities); + + //TODO : OperatorHistory 저장 함수 선언 + void saveAllOperatorHistoryData(List entities); + + //TODO : TechnicalManagerHistory 저장 함수 선언 + void saveAllTechnicalManagerHistoryData(List entities); + + //TODO : BareBoatCharterHistory 저장 함수 선언 + void saveAllBareBoatCharterHistoryData(List entities); + + //TODO : NameHistory 저장 함수 선언 + void saveAllNameHistoryData(List entities); + + //TODO : FlagHistory 저장 함수 선언 + void saveAllFlagHistoryData(List entities); + + //TODO : AdditionalInformation 저장 함수 선언 + void saveAllAdditionalInformationData(List entities); + + //TODO : PandIHistory 저장 함수 선언 + void saveAllPandIHistoryData(List entities); + + //TODO : CallSignAndMmsiHistory 저장 함수 선언 + void saveAllCallSignAndMmsiHistoryData(List entities); + + //TODO : IceClass 저장 함수 선언 + void saveAllIceClassData(List entities); + + //TODO : SafetyManagementCertificateHistory 저장 함수 선언 + void saveAllSafetyManagementCertificateHistoryData(List entities); + + //TODO : ClassHistory 저장 함수 선언 + void saveAllClassHistoryData(List entities); + + //TODO : SurveyDatesHistory 저장 함수 선언 + void saveAllSurveyDatesHistoryData(List entities); + + //TODO : SurveyDatesHistoryUnique 저장 함수 선언 + void saveAllSurveyDatesHistoryUniqueData(List entities); + + //TODO : SisterShipLinks 저장 함수 선언 + void saveAllSisterShipLinksData(List entities); + + //TODO : StatusHistory 저장 함수 선언 + void saveAllStatusHistoryData(List entities); + + //TODO : SpecialFeature 저장 함수 선언 + void saveAllSpecialFeatureData(List entities); + + //TODO : Thrusters 저장 함수 선언 + void saveAllThrustersData(List entities); + void delete(String id); } diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepositoryImpl.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepositoryImpl.java index 247c082..d442518 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepositoryImpl.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailRepositoryImpl.java @@ -1,8 +1,7 @@ package com.snp.batch.jobs.shipdetail.batch.repository; import com.snp.batch.common.batch.repository.BaseJdbcRepository; -import com.snp.batch.jobs.shipdetail.batch.entity.OwnerHistoryEntity; -import com.snp.batch.jobs.shipdetail.batch.entity.ShipDetailEntity; +import com.snp.batch.jobs.shipdetail.batch.entity.*; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; @@ -157,6 +156,21 @@ public class ShipDetailRepositoryImpl extends BaseJdbcRepository entities) { + String entityName = "CrewListEntity"; + String sql = ShipDetailSql.getCrewListSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setCrewListInsertParameters(ps, (CrewListEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllStowageCommodityData(List entities) { + String entityName = "StowageCommodityEntity"; + String sql = ShipDetailSql.getStowageCommoditySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setStowageCommodityInsertParameters(ps, (StowageCommodityEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllGroupBeneficialOwnerHistoryData(List entities) { + String entityName = "GroupBeneficialOwnerHistoryEntity"; + String sql = ShipDetailSql.getGroupBeneficialOwnerHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setGroupBeneficialOwnerHistoryInsertParameters(ps, (GroupBeneficialOwnerHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllShipManagerHistoryData(List entities) { + String entityName = "ShipManagerHistoryEntity"; + String sql = ShipDetailSql.getShipManagerHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setShipManagerHistoryInsertParameters(ps, (ShipManagerHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllOperatorHistoryData(List entities) { + String entityName = "OperatorHistoryEntity"; + String sql = ShipDetailSql.getOperatorHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setOperatorHistoryInsertParameters(ps, (OperatorHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllTechnicalManagerHistoryData(List entities) { + String entityName = "TechnicalManagerHistoryEntity"; + String sql = ShipDetailSql.getTechnicalManagerHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setTechnicalManagerHistoryInsertParameters(ps, (TechnicalManagerHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllBareBoatCharterHistoryData(List entities) { + String entityName = "BareBoatCharterHistoryEntity"; + String sql = ShipDetailSql.getBareBoatCharterHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setBareBoatCharterHistoryInsertParameters(ps, (BareBoatCharterHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllNameHistoryData(List entities) { + String entityName = "NameHistoryEntity"; + String sql = ShipDetailSql.getNameHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setNameHistoryInsertParameters(ps, (NameHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllFlagHistoryData(List entities) { + String entityName = "FlagHistoryEntity"; + String sql = ShipDetailSql.getFlagHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setFlagHistoryInsertParameters(ps, (FlagHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllAdditionalInformationData(List entities) { + String entityName = "AdditionalInformationEntity"; + String sql = ShipDetailSql.getAdditionalInformationSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setAdditionalInformationInsertParameters(ps, (AdditionalInformationEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllPandIHistoryData(List entities) { + String entityName = "PandIHistoryEntity"; + String sql = ShipDetailSql.getPandIHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setPandIHistoryInsertParameters(ps, (PandIHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllCallSignAndMmsiHistoryData(List entities) { + String entityName = "CallSignAndMmsiHistoryEntity"; + String sql = ShipDetailSql.getCallSignAndMmsiHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setCallSignAndMmsiHistoryInsertParameters(ps, (CallSignAndMmsiHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllIceClassData(List entities) { + String entityName = "IceClassEntity"; + String sql = ShipDetailSql.getIceClassSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setIceClassInsertParameters(ps, (IceClassEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllSafetyManagementCertificateHistoryData(List entities) { + String entityName = "SafetyManagementCertificateHistoryEntity"; + String sql = ShipDetailSql.getSafetyManagementCertificateHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setSafetyManagementCertificateHistoryInsertParameters(ps, (SafetyManagementCertificateHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllClassHistoryData(List entities) { + String entityName = "ClassHistoryEntity"; + String sql = ShipDetailSql.getClassHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setClassHistoryInsertParameters(ps, (ClassHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllSurveyDatesHistoryData(List entities) { + String entityName = "SurveyDatesHistoryEntity"; + String sql = ShipDetailSql.getSurveyDatesHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setSurveyDatesHistoryInsertParameters(ps, (SurveyDatesHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllSurveyDatesHistoryUniqueData(List entities) { + String entityName = "SurveyDatesHistoryUniqueEntity"; + String sql = ShipDetailSql.getSurveyDatesHistoryUniqueSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setSurveyDatesHistoryUniqueInsertParameters(ps, (SurveyDatesHistoryUniqueEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllSisterShipLinksData(List entities) { + String entityName = "SisterShipLinksEntity"; + String sql = ShipDetailSql.getSisterShipLinksSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setSisterShipLinksInsertParameters(ps, (SisterShipLinksEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllStatusHistoryData(List entities) { + String entityName = "StatusHistoryEntity"; + String sql = ShipDetailSql.getStatusHistorySql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setStatusHistoryInsertParameters(ps, (StatusHistoryEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllSpecialFeatureData(List entities) { + String entityName = "SpecialFeatureEntity"; + String sql = ShipDetailSql.getSpecialFeatureSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setSpecialFeatureInsertParameters(ps, (SpecialFeatureEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + @Override + public void saveAllThrustersData(List entities) { + String entityName = "ThrustersEntity"; + String sql = ShipDetailSql.getThrustersSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.info("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setThrustersInsertParameters(ps, (ThrustersEntity) entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + public boolean existsByImo(String imo) { String sql = String.format("SELECT COUNT(*) FROM %s WHERE %s = ?", getTableName(), getIdColumnName("ihslrorimoshipno")); Long count = jdbcTemplate.queryForObject(sql, Long.class, imo); @@ -270,6 +788,301 @@ public class ShipDetailRepositoryImpl extends BaseJdbcRepository { .collect(Collectors.toList()); // 1-2. List> -> List (OwnerHistory 데이터 처리용) - // OwnerHistory는 Bulk 처리를 위해 단일 평탄화된 리스트로 만듭니다. - List ownerHistoriyListEntities = items.stream() - .flatMap(item -> { - // ⚠️ ShipDetailUpdate DTO에 List 필드가 존재해야 합니다. - List histories = item.getOwnerHistoryEntityList(); - return histories != null ? histories.stream() : new ArrayList().stream(); - }) - .collect(Collectors.toList()); + List ownerHistoriyListEntities = flattenEntities(items, ShipDetailUpdate::getOwnerHistoryEntityList); + List crewListEntities = flattenEntities(items, ShipDetailUpdate::getCrewListEntityList); + List stowageCommodityListEntities = flattenEntities(items, ShipDetailUpdate::getStowageCommodityEntityList); + List groupBeneficialOwnerHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getGroupBeneficialOwnerHistoryEntityList); + List shipManagerHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getShipManagerHistoryEntityList); + List operatorHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getOperatorHistoryEntityList); + List technicalManagerHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getTechnicalManagerHistoryEntityList); + List bareBoatCharterHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getBareBoatCharterHistoryEntityList); + List nameHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getNameHistoryEntityList); + List flagHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getFlagHistoryEntityList); + List additionalInformationListEntities = flattenEntities(items, ShipDetailUpdate::getAdditionalInformationEntityList); + List pandIHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getPandIHistoryEntityList); + List callSignAndMmsiHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getCallSignAndMmsiHistoryEntityList); + List iceClassListEntities = flattenEntities(items, ShipDetailUpdate::getIceClassEntityList); + List safetyManagementCertificateHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getSafetyManagementCertificateHistoryEntityList); + List classHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getClassHistoryEntityList); + List surveyDatesHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getSurveyDatesHistoryEntityList); + List surveyDatesHistoryUniqueListEntities = flattenEntities(items, ShipDetailUpdate::getSurveyDatesHistoryUniqueEntityList); + List sisterShipLinksListEntities = flattenEntities(items, ShipDetailUpdate::getSisterShipLinksEntityList); + List statusHistoryListEntities = flattenEntities(items, ShipDetailUpdate::getStatusHistoryEntityList); + List specialFeatureListEntities = flattenEntities(items, ShipDetailUpdate::getSpecialFeatureEntityList); + List thrustersListEntities = flattenEntities(items, ShipDetailUpdate::getThrustersEntityList); // 1-3. List (Hash값 데이터 처리용) List hashEntities = items.stream() @@ -63,11 +76,94 @@ public class ShipDetailDataWriter extends BaseWriter { log.debug("Core20 데이터 저장 시작: {} 건", coreEntities.size()); shipDetailRepository.saveAllCoreData(coreEntities); - // ✅ 2-2. OwnerHistory (OwnerHistory 데이터) + // ✅ 2-2. 추가적인 Array/List 데이터 + // OwnerHistory 저장 log.debug("OwnerHistory 데이터 저장 시작: {} 건", ownerHistoriyListEntities.size()); shipDetailRepository.saveAllOwnerHistoryData(ownerHistoriyListEntities); - - // TODO : 추가적인 Array/List 데이터 저장 로직 추가 + + // CrewList 저장 + log.debug("CrewList 데이터 저장 시작: {} 건", crewListEntities.size()); + shipDetailRepository.saveAllCrewListData(crewListEntities); + + // StowageCommodity 저장 + log.debug("StowageCommodity 저장 시작: {} 건", stowageCommodityListEntities.size()); + shipDetailRepository.saveAllStowageCommodityData(stowageCommodityListEntities); + + // GroupBeneficialOwnerHistory 저장 + log.debug("GroupBeneficialOwnerHistory 저장 시작: {} 건", groupBeneficialOwnerHistoryListEntities.size()); + shipDetailRepository.saveAllGroupBeneficialOwnerHistoryData(groupBeneficialOwnerHistoryListEntities); + + // ShipManagerHistory 저장 + log.debug("ShipManagerHistory 저장 시작: {} 건", shipManagerHistoryListEntities.size()); + shipDetailRepository.saveAllShipManagerHistoryData(shipManagerHistoryListEntities); + + // OperatorHistory 저장 + log.debug("OperatorHistory 저장 시작: {} 건", operatorHistoryListEntities.size()); + shipDetailRepository.saveAllOperatorHistoryData(operatorHistoryListEntities); + + // TechnicalManagerHistory 저장 + log.debug("TechnicalManagerHistory 저장 시작: {} 건", technicalManagerHistoryListEntities.size()); + shipDetailRepository.saveAllTechnicalManagerHistoryData(technicalManagerHistoryListEntities); + + // BareBoatCharterHistory 저장 + log.debug("BareBoatCharterHistory 저장 시작: {} 건", bareBoatCharterHistoryListEntities.size()); + shipDetailRepository.saveAllBareBoatCharterHistoryData(bareBoatCharterHistoryListEntities); + + // NameHistory 저장 + log.debug("NameHistory 저장 시작: {} 건", nameHistoryListEntities.size()); + shipDetailRepository.saveAllNameHistoryData(nameHistoryListEntities); + + // FlagHistory 저장 + log.debug("FlagHistory 저장 시작: {} 건", flagHistoryListEntities.size()); + shipDetailRepository.saveAllFlagHistoryData(flagHistoryListEntities); + + // AdditionalInformation 저장 + log.debug("AdditionalInformation 저장 시작: {} 건", additionalInformationListEntities.size()); + shipDetailRepository.saveAllAdditionalInformationData(additionalInformationListEntities); + + // PandIHistory 저장 + log.debug("PandIHistory 저장 시작: {} 건", pandIHistoryListEntities.size()); + shipDetailRepository.saveAllPandIHistoryData(pandIHistoryListEntities); + + // CallSignAndMmsiHistory 저장 + log.debug("CallSignAndMmsiHistory 저장 시작: {} 건", callSignAndMmsiHistoryListEntities.size()); + shipDetailRepository.saveAllCallSignAndMmsiHistoryData(callSignAndMmsiHistoryListEntities); + + // IceClass 저장 + log.debug("IceClass 저장 시작: {} 건", iceClassListEntities.size()); + shipDetailRepository.saveAllIceClassData(iceClassListEntities); + + // SafetyManagementCertificateHistory 저장 + log.debug("SafetyManagementCertificateHistory 저장 시작: {} 건", safetyManagementCertificateHistoryListEntities.size()); + shipDetailRepository.saveAllSafetyManagementCertificateHistoryData(safetyManagementCertificateHistoryListEntities); + + // ClassHistory 저장 + log.debug("ClassHistory 저장 시작: {} 건", classHistoryListEntities.size()); + shipDetailRepository.saveAllClassHistoryData(classHistoryListEntities); + + // SurveyDatesHistory 저장 + log.debug("SurveyDatesHistory 저장 시작: {} 건", surveyDatesHistoryListEntities.size()); + shipDetailRepository.saveAllSurveyDatesHistoryData(surveyDatesHistoryListEntities); + + // SurveyDatesHistoryUnique 저장 + log.debug("SurveyDatesHistoryUnique 저장 시작: {} 건", surveyDatesHistoryUniqueListEntities.size()); + shipDetailRepository.saveAllSurveyDatesHistoryUniqueData(surveyDatesHistoryUniqueListEntities); + + // SisterShipLinks 저장 + log.debug("SisterShipLinks 저장 시작: {} 건", sisterShipLinksListEntities.size()); + shipDetailRepository.saveAllSisterShipLinksData(sisterShipLinksListEntities); + + // StatusHistory 저장 + log.debug("StatusHistory 저장 시작: {} 건", statusHistoryListEntities.size()); + shipDetailRepository.saveAllStatusHistoryData(statusHistoryListEntities); + + // SpecialFeature 저장 + log.debug("SpecialFeature 저장 시작: {} 건", specialFeatureListEntities.size()); + shipDetailRepository.saveAllSpecialFeatureData(specialFeatureListEntities); + + // Thrusters 저장 + log.debug("Thrusters 저장 시작: {} 건", thrustersListEntities.size()); + shipDetailRepository.saveAllThrustersData(thrustersListEntities); // ✅ 2-3. ShipHashRepository (Hash값 데이터) log.debug("Ship Hash 데이터 저장 시작: {} 건", hashEntities.size()); @@ -77,4 +173,26 @@ public class ShipDetailDataWriter extends BaseWriter { } + /** + * List에서 특정 Entity List들을 추출하여 하나의 List로 병합(Flatten)합니다. + * + * @param items 처리할 ShipDetailUpdate 리스트 + * @param entityListGetter ShipDetailUpdate에서 원하는 Entity List를 가져오는 Function (예: ShipDetailUpdate::getCrewListEntityList) + * @param Entity의 타입 + * @return 평탄화된 Entity 리스트 + */ + public static List flattenEntities( + List items, + Function> entityListGetter) { + + return items.stream() + // DTO에서 특정 Entity List를 추출합니다. + .map(entityListGetter) + // List가 null인 경우는 필터링하여 NPE를 방지합니다. + .filter(list -> list != null) + // List> 형태를 List 형태로 평탄화합니다. + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + } diff --git a/src/main/java/com/snp/batch/service/BatchDateService.java b/src/main/java/com/snp/batch/service/BatchDateService.java new file mode 100644 index 0000000..3ab3c66 --- /dev/null +++ b/src/main/java/com/snp/batch/service/BatchDateService.java @@ -0,0 +1,66 @@ +package com.snp.batch.service; +import com.snp.batch.global.model.BatchLastExecution; +import com.snp.batch.global.repository.BatchLastExecutionRepository; +import jakarta.transaction.Transactional; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.*; + +@Service +public class BatchDateService { + private static final String API_KEY = "SHIP_UPDATE_API"; + private final BatchLastExecutionRepository repository; + + public BatchDateService(BatchLastExecutionRepository repository) { + this.repository = repository; + } + + /** + * API 호출에 필요한 from/to 날짜 파라미터를 계산하고 반환합니다. + */ + public Map getShipUpdateApiDateParams() { + // 1. 마지막 성공 일자 (FROM 날짜)를 DB에서 조회 + // 조회된 값이 없으면 (최초 실행), API 호출 시점의 하루 전 날짜를 사용합니다. + LocalDate lastDate = repository.findLastSuccessDate(API_KEY) + .orElse(LocalDate.now().minusDays(1)); + + // 2. 현재 실행 시점의 일자 (TO 날짜) 계산 + LocalDate currentDate = LocalDate.now(); + + // 3. 파라미터 Map 구성 + Map params = new HashMap<>(); + + // FROM Parameters (DB 조회 값) + params.put("fromYear", String.valueOf(lastDate.getYear())); + params.put("fromMonth", String.valueOf(lastDate.getMonthValue())); + params.put("fromDay", String.valueOf(lastDate.getDayOfMonth())); + + // TO Parameters (현재 시점 값) + params.put("toYear", String.valueOf(currentDate.getYear())); + params.put("toMonth", String.valueOf(currentDate.getMonthValue())); + params.put("toDay", String.valueOf(currentDate.getDayOfMonth())); + + // 고정 값 + params.put("shipsCategory", "0"); + + return params; + } + + /** + * 배치 성공 시, 다음 실행을 위해 to 날짜를 DB에 저장 및 업데이트합니다. + * @param successDate API 호출 성공 시 사용된 to 날짜 + */ + @Transactional // UPDATE 쿼리를 사용하므로 트랜잭션 필요 + public void updateLastSuccessDate(LocalDate successDate) { + + // 1. UPDATE 시도 + int updatedRows = repository.updateLastSuccessDate(API_KEY, successDate); + + // 2. 업데이트된 레코드가 없다면 (최초 실행), INSERT 수행 + if (updatedRows == 0) { + BatchLastExecution entity = new BatchLastExecution(API_KEY, successDate); + repository.save(entity); + } + } +}