diff --git a/src/main/java/com/snp/batch/global/config/MaritimeApiWebClientConfig.java b/src/main/java/com/snp/batch/global/config/MaritimeApiWebClientConfig.java index ddeb443..d05a2f9 100644 --- a/src/main/java/com/snp/batch/global/config/MaritimeApiWebClientConfig.java +++ b/src/main/java/com/snp/batch/global/config/MaritimeApiWebClientConfig.java @@ -64,7 +64,7 @@ public class MaritimeApiWebClientConfig { .defaultHeaders(headers -> headers.setBasicAuth(maritimeApiUsername, maritimeApiPassword)) .codecs(configurer -> configurer .defaultCodecs() - .maxInMemorySize(30 * 1024 * 1024)) // 30MB 버퍼 + .maxInMemorySize(100 * 1024 * 1024)) // 30MB 버퍼 .build(); } diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/config/PscInspectionJobConfig.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/config/PscInspectionJobConfig.java new file mode 100644 index 0000000..3cf87f0 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/config/PscInspectionJobConfig.java @@ -0,0 +1,118 @@ +package com.snp.batch.jobs.pscInspection.batch.config; + +import com.snp.batch.common.batch.config.BaseJobConfig; +import com.snp.batch.jobs.pscInspection.batch.dto.PscInspectionDto; +import com.snp.batch.jobs.pscInspection.batch.entity.PscInspectionEntity; +import com.snp.batch.jobs.pscInspection.batch.processor.PscInspectionProcessor; +import com.snp.batch.jobs.pscInspection.batch.reader.PscApiReader; +import com.snp.batch.jobs.pscInspection.batch.writer.PscInspectionWriter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.StepScope; +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 org.springframework.beans.factory.annotation.Value; + +/** + * 선박 상세 정보 Import Job Config + * + * 특징: + * - ship_data 테이블에서 IMO 번호 조회 + * - IMO 번호를 100개씩 배치로 분할 + * - Maritime API GetShipsByIHSLRorIMONumbers 호출 + * TODO : GetShipsByIHSLRorIMONumbersAll 호출로 변경 + * - 선박 상세 정보를 ship_detail 테이블에 저장 (UPSERT) + * + * 데이터 흐름: + * ShipMovementReader (ship_data → Maritime API) + * ↓ (PortCallDto) + * ShipMovementProcessor + * ↓ (ShipMovementEntity) + * ShipDetailDataWriter + * ↓ (ship_movement 테이블) + */ + +@Slf4j +@Configuration +public class PscInspectionJobConfig extends BaseJobConfig { + + private final PscInspectionProcessor pscInspectionProcessor; + private final PscInspectionWriter pscInspectionWriter; + private final JdbcTemplate jdbcTemplate; + private final WebClient maritimeApiWebClient; + + public PscInspectionJobConfig( + JobRepository jobRepository, + PlatformTransactionManager transactionManager, + PscInspectionProcessor pscInspectionProcessor, + PscInspectionWriter pscInspectionWriter, + JdbcTemplate jdbcTemplate, + @Qualifier("maritimeApiWebClient") WebClient maritimeApiWebClient, PscApiReader pscApiReader) { // ObjectMapper 주입 추가 + super(jobRepository, transactionManager); + this.pscInspectionProcessor = pscInspectionProcessor; + this.pscInspectionWriter = pscInspectionWriter; + this.jdbcTemplate = jdbcTemplate; + this.maritimeApiWebClient = maritimeApiWebClient; + } + + + + @Override + protected String getJobName() { + return "PSCDetailImportJob"; + } + + @Override + protected String getStepName() { + return "PSCDetailImportStep"; + } + + @Bean + @StepScope + public PscApiReader pscApiReader( + @Qualifier("maritimeApiWebClient") WebClient webClient, + @Value("#{jobParameters['fromDate']}") String fromDate, + @Value("#{jobParameters['toDate']}") String toDate + ) { + return new PscApiReader(webClient, fromDate, toDate); + } + + @Override + protected ItemReader createReader() { + return pscApiReader(null, null, null); + } + + @Override + protected ItemProcessor createProcessor() { + return pscInspectionProcessor; + } + + @Override + protected ItemWriter createWriter() { // 타입 변경 + return pscInspectionWriter; + } + + @Override + protected int getChunkSize() { + return 10; // API에서 100개씩 가져오므로 chunk도 100으로 설정 + } + + @Bean(name = "PSCDetailImportJob") + public Job PSCDetailImportJob() { + return job(); + } + + @Bean(name = "PSCDetailImportStep") + public Step PSCDetailImportStep() { + return step(); + } +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscAllCertificateDto.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscAllCertificateDto.java new file mode 100644 index 0000000..42540d6 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscAllCertificateDto.java @@ -0,0 +1,75 @@ +package com.snp.batch.jobs.pscInspection.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class PscAllCertificateDto { + + @JsonProperty("Type_Id") + private String typeId; + + @JsonProperty("DataSetVersion") + private PscDataSetVersionDto dataSetVersion; + + @JsonProperty("Certificate_ID") + private String certificateId; + + @JsonProperty("Inspection_ID") + private String inspectionId; + + @JsonProperty("Lrno") + private String lrno; + + @JsonProperty("Certificate_Title_Code") + private String certificateTitleCode; + + @JsonProperty("Certificate_Title") + private String certificateTitle; + + @JsonProperty("Issuing_Authority_Code") + private String issuingAuthorityCode; + + @JsonProperty("Issuing_Authority") + private String issuingAuthority; + + @JsonProperty("Class_Soc_of_Issuer") + private String classSocOfIssuer; + + @JsonProperty("Other_Issuing_Authority") + private String otherIssuingAuthority; + + @JsonProperty("Issue_Date") + private String issueDate; + + @JsonProperty("Expiry_Date") + private String expiryDate; + + @JsonProperty("Last_Survey_Date") + private String lastSurveyDate; + + @JsonProperty("Survey_Authority_Code") + private String surveyAuthorityCode; + + @JsonProperty("Survey_Authority") + private String surveyAuthority; + + @JsonProperty("Other_Survey_Authority") + private String otherSurveyAuthority; + + @JsonProperty("Latest_Survey_Place") + private String latestSurveyPlace; + + @JsonProperty("Latest_Survey_Place_Code") + private String latestSurveyPlaceCode; + + @JsonProperty("Survey_Authority_Type") + private String surveyAuthorityType; + + @JsonProperty("Inspection_Date") + private String inspectionDate; + + @JsonProperty("Inspected_By") + private String inspectedBy; + +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscApiResponseDto.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscApiResponseDto.java new file mode 100644 index 0000000..1d3af2a --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscApiResponseDto.java @@ -0,0 +1,18 @@ +package com.snp.batch.jobs.pscInspection.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class PscApiResponseDto { + + @JsonProperty("Inspections") + private List inspections; + @JsonProperty("inspectionCount") + private Integer inspectionCount; + + @JsonProperty("APSStatus") + private PscApsStatusDto apsStatus; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscApsStatusDto.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscApsStatusDto.java new file mode 100644 index 0000000..8e7f20f --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscApsStatusDto.java @@ -0,0 +1,31 @@ +package com.snp.batch.jobs.pscInspection.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class PscApsStatusDto { + @JsonProperty("SystemVersion") + private String systemVersion; + + @JsonProperty("SystemDate") + private String systemDate; + + @JsonProperty("JobRunDate") + private String jobRunDate; + + @JsonProperty("CompletedOK") + private Boolean completedOK; + + @JsonProperty("ErrorLevel") + private String errorLevel; + + @JsonProperty("ErrorMessage") + private String errorMessage; + + @JsonProperty("RemedialAction") + private String remedialAction; + + @JsonProperty("Guid") + private String guid; +} \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscCertificateDto.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscCertificateDto.java new file mode 100644 index 0000000..7a248b4 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscCertificateDto.java @@ -0,0 +1,67 @@ +package com.snp.batch.jobs.pscInspection.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class PscCertificateDto { + @JsonProperty("Type_Id") + private String typeId; + + @JsonProperty("DataSetVersion") + private PscDataSetVersionDto dataSetVersion; + + @JsonProperty("Certificate_ID") + private String certificateId; + + @JsonProperty("Certificate_Title") + private String certificateTitle; + + @JsonProperty("Certificate_Title_Code") + private String certificateTitleCode; + + @JsonProperty("Class_SOC_Of_Issuer") + private String classSocOfIssuer; + + @JsonProperty("Expiry_Date") + private String expiryDate; // ISO 날짜 문자열 그대로 받음 + + @JsonProperty("Inspection_ID") + private String inspectionId; + + @JsonProperty("Issue_Date") + private String issueDate; + + @JsonProperty("Issuing_Authority") + private String issuingAuthority; + + @JsonProperty("Issuing_Authority_Code") + private String issuingAuthorityCode; + + @JsonProperty("Last_Survey_Date") + private String lastSurveyDate; + + @JsonProperty("Latest_Survey_Place") + private String latestSurveyPlace; + + @JsonProperty("Latest_Survey_Place_Code") + private String latestSurveyPlaceCode; + + @JsonProperty("Lrno") + private String lrno; + + @JsonProperty("Other_Issuing_Authority") + private String otherIssuingAuthority; + + @JsonProperty("Other_Survey_Authority") + private String otherSurveyAuthority; + + @JsonProperty("Survey_Authority") + private String surveyAuthority; + + @JsonProperty("Survey_Authority_Code") + private String surveyAuthorityCode; + + @JsonProperty("Survey_Authority_Type") + private String surveyAuthorityType; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscDataSetVersionDto.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscDataSetVersionDto.java new file mode 100644 index 0000000..41fe574 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscDataSetVersionDto.java @@ -0,0 +1,10 @@ +package com.snp.batch.jobs.pscInspection.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class PscDataSetVersionDto { + @JsonProperty("DataSetVersion") + private String dataSetVersion; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscDefectDto.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscDefectDto.java new file mode 100644 index 0000000..3557833 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscDefectDto.java @@ -0,0 +1,91 @@ +package com.snp.batch.jobs.pscInspection.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class PscDefectDto { + @JsonProperty("Type_Id") + private String typeId; + + @JsonProperty("DataSetVersion") + private PscDataSetVersionDto dataSetVersion; + + @JsonProperty("Action_1") + private String action1; + + @JsonProperty("Action_2") + private String action2; + + @JsonProperty("Action_3") + private String action3; + + @JsonProperty("Action_Code_1") + private String actionCode1; + + @JsonProperty("Action_Code_2") + private String actionCode2; + + @JsonProperty("Action_Code_3") + private String actionCode3; + + @JsonProperty("AmsA_Action_Code_1") + private String amsaActionCode1; + + @JsonProperty("AmsA_Action_Code_2") + private String amsaActionCode2; + + @JsonProperty("AmsA_Action_Code_3") + private String amsaActionCode3; + + @JsonProperty("Class_Is_Responsible") + private String classIsResponsible; + + @JsonProperty("Defect_Code") + private String defectCode; + + @JsonProperty("Defect_ID") + private String defectId; + + @JsonProperty("Defect_Text") + private String defectText; + + @JsonProperty("Defective_Item_Code") + private String defectiveItemCode; + + @JsonProperty("Detention_Reason_Deficiency") + private String detentionReasonDeficiency; + + @JsonProperty("Inspection_ID") + private String inspectionId; + + @JsonProperty("Main_Defect_Code") + private String mainDefectCode; + + @JsonProperty("Main_Defect_Text") + private String mainDefectText; + + @JsonProperty("Nature_Of_Defect_Code") + private String natureOfDefectCode; + + @JsonProperty("Nature_Of_Defect_DeCode") + private String natureOfDefectDecode; + + @JsonProperty("Other_Action") + private String otherAction; + + @JsonProperty("Other_Recognised_Org_Resp") + private String otherRecognisedOrgResp; + + @JsonProperty("Recognised_Org_Resp") + private String recognisedOrgResp; + + @JsonProperty("Recognised_Org_Resp_Code") + private String recognisedOrgRespCode; + + @JsonProperty("Recognised_Org_Resp_YN") + private String recognisedOrgRespYn; + + @JsonProperty("IsAccidentalDamage") + private String isAccidentalDamage; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscInspectionDto.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscInspectionDto.java new file mode 100644 index 0000000..6d8ef89 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/dto/PscInspectionDto.java @@ -0,0 +1,122 @@ +package com.snp.batch.jobs.pscInspection.batch.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Data +public class PscInspectionDto { + + @JsonProperty("typeId") + private String typeId; + + @JsonProperty("DataSetVersion") + private PscDataSetVersionDto dataSetVersion; + + @JsonProperty("Authorisation") + private String authorisation; + + @JsonProperty("CallSign") + private String callSign; + + @JsonProperty("Cargo") + private String cargo; + + @JsonProperty("Charterer") + private String charterer; + + @JsonProperty("Class") + private String shipClass; + + @JsonProperty("Country") + private String country; + + @JsonProperty("Inspection_Date") + private String inspectionDate; + + @JsonProperty("Release_Date") + private String releaseDate; + + @JsonProperty("Ship_Detained") + private String shipDetained; + + @JsonProperty("Dead_Weight") + private String deadWeight; + + @JsonProperty("Expanded_Inspection") + private String expandedInspection; + + @JsonProperty("Flag") + private String flag; + + @JsonProperty("Follow_Up_Inspection") + private String followUpInspection; + + @JsonProperty("Gross_Tonnage") + private String grossTonnage; + + @JsonProperty("Inspection_ID") + private String inspectionId; + + @JsonProperty("Inspection_Port_Code") + private String inspectionPortCode; + + @JsonProperty("Inspection_Port_Decode") + private String inspectionPortDecode; + + @JsonProperty("Keel_Laid") + private String keelLaid; + + @JsonProperty("Last_Updated") + private String lastUpdated; + + @JsonProperty("IHSLR_or_IMO_Ship_No") + private String ihslrOrImoShipNo; + + @JsonProperty("Manager") + private String manager; + + @JsonProperty("Number_Of_Days_Detained") + private Integer numberOfDaysDetained; + + @JsonProperty("Number_Of_Defects") + private String numberOfDefects; + + @JsonProperty("Number_Of_Part_Days_Detained") + private BigDecimal numberOfPartDaysDetained; + + @JsonProperty("Other_Inspection_Type") + private String otherInspectionType; + + @JsonProperty("Owner") + private String owner; + + @JsonProperty("Ship_Name") + private String shipName; + + @JsonProperty("Ship_Type_Code") + private String shipTypeCode; + + @JsonProperty("Ship_Type_Decode") + private String shipTypeDecode; + + @JsonProperty("Source") + private String source; + + @JsonProperty("UNLOCODE") + private String unlocode; + + @JsonProperty("Year_Of_Build") + private String yearOfBuild; + + @JsonProperty("PSCDefects") + private List pscDefects; + + @JsonProperty("PSCCertificates") + private List pscCertificates; + + @JsonProperty("PSCAllCertificates") + private List pscAllCertificates; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscAllCertificateEntity.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscAllCertificateEntity.java new file mode 100644 index 0000000..f750520 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscAllCertificateEntity.java @@ -0,0 +1,48 @@ +package com.snp.batch.jobs.pscInspection.batch.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class PscAllCertificateEntity { + + private String certificateId; + + private String typeId; + private String dataSetVersion; + + private String inspectionId; + private String lrno; + + private String certificateTitleCode; + private String certificateTitle; + + private String issuingAuthorityCode; + private String issuingAuthority; + + private String classSocOfIssuer; + private String otherIssuingAuthority; + + private LocalDateTime issueDate; + private LocalDateTime expiryDate; + private LocalDateTime lastSurveyDate; + + private String surveyAuthorityCode; + private String surveyAuthority; + private String otherSurveyAuthority; + + private String latestSurveyPlace; + private String latestSurveyPlaceCode; + + private String surveyAuthorityType; + + private String inspectionDate; + private String inspectedBy; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscCertificateEntity.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscCertificateEntity.java new file mode 100644 index 0000000..d360916 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscCertificateEntity.java @@ -0,0 +1,45 @@ +package com.snp.batch.jobs.pscInspection.batch.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class PscCertificateEntity { + + private String certificateId; + + private String typeId; + private String dataSetVersion; + + private String certificateTitle; + private String certificateTitleCode; + + private String classSocOfIssuer; + + private LocalDateTime expiryDate; + private String inspectionId; + private LocalDateTime issueDate; + + private String issuingAuthority; + private String issuingAuthorityCode; + + private LocalDateTime lastSurveyDate; + private String latestSurveyPlace; + private String latestSurveyPlaceCode; + + private String lrno; + + private String otherIssuingAuthority; + private String otherSurveyAuthority; + + private String surveyAuthority; + private String surveyAuthorityCode; + private String surveyAuthorityType; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscDefectEntity.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscDefectEntity.java new file mode 100644 index 0000000..e84278e --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscDefectEntity.java @@ -0,0 +1,53 @@ +package com.snp.batch.jobs.pscInspection.batch.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class PscDefectEntity { + + private String defectId; + + private String typeId; + private String dataSetVersion; + + private String action1; + private String action2; + private String action3; + private String actionCode1; + private String actionCode2; + private String actionCode3; + + private String amsaActionCode1; + private String amsaActionCode2; + private String amsaActionCode3; + + private String classIsResponsible; + + private String defectCode; + private String defectText; + + private String defectiveItemCode; + private String detentionReasonDeficiency; + + private String inspectionId; + + private String mainDefectCode; + private String mainDefectText; + + private String natureOfDefectCode; + private String natureOfDefectDecode; + + private String otherAction; + private String otherRecognisedOrgResp; + private String recognisedOrgResp; + private String recognisedOrgRespCode; + private String recognisedOrgRespYn; + + private String isAccidentalDamage; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscInspectionEntity.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscInspectionEntity.java new file mode 100644 index 0000000..040f3ae --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/entity/PscInspectionEntity.java @@ -0,0 +1,64 @@ +package com.snp.batch.jobs.pscInspection.batch.entity; + +import com.snp.batch.jobs.pscInspection.batch.dto.PscAllCertificateDto; +import com.snp.batch.jobs.pscInspection.batch.dto.PscCertificateDto; +import com.snp.batch.jobs.pscInspection.batch.dto.PscDefectDto; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class PscInspectionEntity { + + private String typeId; + private String dataSetVersion; + private String authorisation; + private String callSign; + private String shipClass; + private String cargo; + private String charterer; + private String country; + private LocalDateTime inspectionDate; + private LocalDateTime releaseDate; + private String shipDetained; + private String deadWeight; + private String expandedInspection; + private String flag; + private String followUpInspection; + private String grossTonnage; + + private String inspectionId; + + private String inspectionPortCode; + private String inspectionPortDecode; + + private String keelLaid; + private LocalDateTime lastUpdated; + private String ihslrOrImoShipNo; + private String manager; + + private Integer numberOfDaysDetained; + private String numberOfDefects; + private BigDecimal numberOfPartDaysDetained; + + private String otherInspectionType; + private String owner; + private String shipName; + private String shipTypeCode; + private String shipTypeDecode; + private String source; + private String unlocode; + private String yearOfBuild; + + private List defects; + private List certificates; + private List allCertificates; +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/processor/PscInspectionProcessor.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/processor/PscInspectionProcessor.java new file mode 100644 index 0000000..a47362d --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/processor/PscInspectionProcessor.java @@ -0,0 +1,268 @@ +package com.snp.batch.jobs.pscInspection.batch.processor; + +import com.snp.batch.common.batch.processor.BaseProcessor; +import com.snp.batch.jobs.pscInspection.batch.dto.*; +import com.snp.batch.jobs.pscInspection.batch.entity.PscAllCertificateEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscCertificateEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscDefectEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscInspectionEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static jakarta.xml.bind.DatatypeConverter.parseDateTime; + +@Slf4j +@Component +public class PscInspectionProcessor extends BaseProcessor { + + @Override + public PscInspectionEntity processItem(PscInspectionDto item) throws Exception { + + PscInspectionEntity entity = new PscInspectionEntity(); + + entity.setTypeId(s(item.getTypeId())); + entity.setDataSetVersion(item.getDataSetVersion() != null ? s(item.getDataSetVersion().getDataSetVersion()) : null); + entity.setAuthorisation(s(item.getAuthorisation())); + entity.setCallSign(s(item.getCallSign())); + entity.setShipClass(s(item.getShipClass())); + entity.setCargo(s(item.getCargo())); + entity.setCharterer(s(item.getCharterer())); + entity.setCountry(s(item.getCountry())); + + entity.setInspectionDate(dt(item.getInspectionDate())); + entity.setReleaseDate(dt(item.getReleaseDate())); + entity.setShipDetained(s(item.getShipDetained())); + entity.setDeadWeight(s(item.getDeadWeight())); + + entity.setExpandedInspection(s(item.getExpandedInspection())); + entity.setFlag(s(item.getFlag())); + entity.setFollowUpInspection(s(item.getFollowUpInspection())); + entity.setGrossTonnage(s(item.getGrossTonnage())); + + entity.setInspectionId(s(item.getInspectionId())); + entity.setInspectionPortCode(s(item.getInspectionPortCode())); + entity.setInspectionPortDecode(s(item.getInspectionPortDecode())); + + entity.setKeelLaid(s(item.getKeelLaid())); + entity.setLastUpdated(dt(item.getLastUpdated())); + entity.setIhslrOrImoShipNo(s(item.getIhslrOrImoShipNo())); + entity.setManager(s(item.getManager())); + + entity.setNumberOfDaysDetained(i(item.getNumberOfDaysDetained())); + entity.setNumberOfDefects(s(item.getNumberOfDefects())); + entity.setNumberOfPartDaysDetained(bd(item.getNumberOfPartDaysDetained())); + + entity.setOtherInspectionType(s(item.getOtherInspectionType())); + entity.setOwner(s(item.getOwner())); + entity.setShipName(s(item.getShipName())); + entity.setShipTypeCode(s(item.getShipTypeCode())); + entity.setShipTypeDecode(s(item.getShipTypeDecode())); + entity.setSource(s(item.getSource())); + entity.setUnlocode(s(item.getUnlocode())); + entity.setYearOfBuild(s(item.getYearOfBuild())); + + // 리스트 null-safe + entity.setDefects(item.getPscDefects() == null ? List.of() : convertDefectDtos(item.getPscDefects())); + entity.setCertificates(item.getPscCertificates() == null ? List.of() : convertCertificateDtos(item.getPscCertificates())); + entity.setAllCertificates(item.getPscAllCertificates() == null ? List.of() : convertAllCertificateDtos(item.getPscAllCertificates())); + + + return entity; + } + + + /** ----------------------- 공통 메서드 ----------------------- */ + + private String s(Object v) { + return (v == null) ? null : v.toString().trim(); + } + + private Boolean b(Object v) { + if (v == null) return null; + String s = v.toString().trim().toLowerCase(); + if (s.equals("true") || s.equals("t") || s.equals("1")) return true; + if (s.equals("false") || s.equals("f") || s.equals("0")) return false; + return null; + } + private BigDecimal bd(Object v) { + if (v == null) return null; + try { + return new BigDecimal(v.toString().trim()); + } catch (Exception e) { + return null; + } + } + private Integer i(Object v) { + if (v == null) return null; + try { + return Integer.parseInt(v.toString().trim()); + } catch (Exception e) { + return null; + } + } + + private Double d(Object v) { + if (v == null) return null; + try { + return Double.parseDouble(v.toString().trim()); + } catch (Exception e) { + return null; + } + } + + private LocalDateTime dt(String dateStr) { + if (dateStr == null || dateStr.isBlank()) return null; + + // 가장 흔한 ISO 형태 + try { + return LocalDateTime.parse(dateStr); + } catch (Exception ignored) {} + + // yyyy-MM-dd + try { + return LocalDate.parse(dateStr).atStartOfDay(); + } catch (Exception ignored) {} + + // yyyy-MM-dd HH:mm:ss + try { + return LocalDateTime.parse(dateStr, + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } catch (Exception ignored) {} + + // yyyy-MM-ddTHH:mm:ssZ 형태 + try { + return OffsetDateTime.parse(dateStr).toLocalDateTime(); + } catch (Exception ignored) {} + + log.warn("⚠️ 날짜 변환 실패 → {}", dateStr); + return null; + } + + public static List convertDefectDtos(List dtos) { + if (dtos == null || dtos.isEmpty()) return List.of(); + + return dtos.stream() + .map(dto -> PscDefectEntity.builder() + .defectId(dto.getDefectId()) + .inspectionId(dto.getInspectionId()) + .typeId(dto.getTypeId()) + .dataSetVersion(dto.getDataSetVersion() != null ? dto.getDataSetVersion().getDataSetVersion() : null) + .action1(dto.getAction1()) + .action2(dto.getAction2()) + .action3(dto.getAction3()) + .actionCode1(dto.getActionCode1()) + .actionCode2(dto.getActionCode2()) + .actionCode3(dto.getActionCode3()) + .amsaActionCode1(dto.getAmsaActionCode1()) + .amsaActionCode2(dto.getAmsaActionCode2()) + .amsaActionCode3(dto.getAmsaActionCode3()) + .classIsResponsible(dto.getClassIsResponsible()) + .defectCode(dto.getDefectCode()) + .defectText(dto.getDefectText()) + .defectiveItemCode(dto.getDefectiveItemCode()) + .detentionReasonDeficiency(dto.getDetentionReasonDeficiency()) + .mainDefectCode(dto.getMainDefectCode()) + .mainDefectText(dto.getMainDefectText()) + .natureOfDefectCode(dto.getNatureOfDefectCode()) + .natureOfDefectDecode(dto.getNatureOfDefectDecode()) + .otherAction(dto.getOtherAction()) + .otherRecognisedOrgResp(dto.getOtherRecognisedOrgResp()) + .recognisedOrgResp(dto.getRecognisedOrgResp()) + .recognisedOrgRespCode(dto.getRecognisedOrgRespCode()) + .recognisedOrgRespYn(dto.getRecognisedOrgRespYn()) + .isAccidentalDamage(dto.getIsAccidentalDamage()) + .build()) + .collect(Collectors.toList()); + } + private List convertCertificateDtos(List dtos) { + if (dtos == null || dtos.isEmpty()) return List.of(); + + return dtos.stream() + .map(dto -> PscCertificateEntity.builder() + .certificateId(dto.getCertificateId()) + .typeId(dto.getTypeId()) + .dataSetVersion(dto.getDataSetVersion() != null ? dto.getDataSetVersion().getDataSetVersion() : null) + .certificateTitle(dto.getCertificateTitle()) + .certificateTitleCode(dto.getCertificateTitleCode()) + .classSocOfIssuer(dto.getClassSocOfIssuer()) + .issueDate(dto.getIssueDate() != null ? parseFlexible(dto.getIssueDate()) : null) + .expiryDate(dto.getExpiryDate() != null ? parseFlexible(dto.getExpiryDate()) : null) + .inspectionId(dto.getInspectionId()) + .issuingAuthority(dto.getIssuingAuthority()) + .issuingAuthorityCode(dto.getIssuingAuthorityCode()) + .lastSurveyDate(dto.getLastSurveyDate() != null ? parseFlexible(dto.getLastSurveyDate()) : null) + .latestSurveyPlace(dto.getLatestSurveyPlace()) + .latestSurveyPlaceCode(dto.getLatestSurveyPlaceCode()) + .lrno(dto.getLrno()) + .otherIssuingAuthority(dto.getOtherIssuingAuthority()) + .otherSurveyAuthority(dto.getOtherSurveyAuthority()) + .surveyAuthority(dto.getSurveyAuthority()) + .surveyAuthorityCode(dto.getSurveyAuthorityCode()) + .surveyAuthorityType(dto.getSurveyAuthorityType()) + .build()) + .collect(Collectors.toList()); + } + + public static List convertAllCertificateDtos(List dtos) { + if (dtos == null || dtos.isEmpty()) return List.of(); + + return dtos.stream() + .map(dto -> PscAllCertificateEntity.builder() + .certificateId(dto.getCertificateId()) + .typeId(dto.getTypeId()) + .dataSetVersion(dto.getDataSetVersion() != null ? dto.getDataSetVersion().getDataSetVersion() : null) + .inspectionId(dto.getInspectionId()) + .lrno(dto.getLrno()) + .certificateTitleCode(dto.getCertificateTitleCode()) + .certificateTitle(dto.getCertificateTitle()) + .issuingAuthorityCode(dto.getIssuingAuthorityCode()) + .issuingAuthority(dto.getIssuingAuthority()) + .classSocOfIssuer(dto.getClassSocOfIssuer()) + .otherIssuingAuthority(dto.getOtherIssuingAuthority()) + .issueDate(dto.getIssueDate() != null ? parseFlexible(dto.getIssueDate()) : null) + .expiryDate(dto.getExpiryDate() != null ? parseFlexible(dto.getExpiryDate()) : null) + .lastSurveyDate(dto.getLastSurveyDate() != null ? parseFlexible(dto.getLastSurveyDate()) : null) + .surveyAuthorityCode(dto.getSurveyAuthorityCode()) + .surveyAuthority(dto.getSurveyAuthority()) + .otherSurveyAuthority(dto.getOtherSurveyAuthority()) + .latestSurveyPlace(dto.getLatestSurveyPlace()) + .latestSurveyPlaceCode(dto.getLatestSurveyPlaceCode()) + .surveyAuthorityType(dto.getSurveyAuthorityType()) + .inspectionDate(dto.getInspectionDate()) + .inspectedBy(dto.getInspectedBy()) + .build()) + .collect(Collectors.toList()); + } + + private static final List FORMATTERS = Arrays.asList( + DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"), + DateTimeFormatter.ISO_LOCAL_DATE_TIME + ); + + public static LocalDateTime parseFlexible(String dateStr) { + if (dateStr == null || dateStr.isEmpty()) return null; + + for (DateTimeFormatter formatter : FORMATTERS) { + try { + return LocalDateTime.parse(dateStr, formatter); + } catch (DateTimeParseException ignored) { + // 포맷 실패 시 다음 시도 + } + } + // 모두 실패 시 null 반환 + System.err.println("날짜 파싱 실패: " + dateStr); + return null; + } +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/reader/PscApiReader.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/reader/PscApiReader.java new file mode 100644 index 0000000..70a8f81 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/reader/PscApiReader.java @@ -0,0 +1,173 @@ +package com.snp.batch.jobs.pscInspection.batch.reader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.snp.batch.common.batch.reader.BaseApiReader; +import com.snp.batch.jobs.pscInspection.batch.dto.PscApiResponseDto; +import com.snp.batch.jobs.pscInspection.batch.dto.PscInspectionDto; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.Collections; +import java.util.List; + +@Slf4j +@StepScope +public class PscApiReader extends BaseApiReader { + + //private final JdbcTemplate jdbcTemplate; + + private final String fromDate; + private final String toDate; +// private List allImoNumbers; + private List allData; + private int currentBatchIndex = 0; + private final int batchSize = 10; + + public PscApiReader(@Qualifier("maritimeApiWebClient") WebClient webClient, + @Value("#{jobParameters['fromDate']}") String fromDate, + @Value("#{jobParameters['toDate']}") String toDate) { + super(webClient); + //this.jdbcTemplate = jdbcTemplate; + this.fromDate = fromDate; + this.toDate = toDate; + enableChunkMode(); + } + + @Override + protected String getReaderName() { + return "PscApiReader"; + } + + @Override + protected void resetCustomState() { + this.currentBatchIndex = 0; +// this.allImoNumbers = null; + } + + @Override + protected String getApiPath() { + return "/MaritimeWCF/PSCService.svc/RESTFul/GetPSCDataByLastUpdateDateRange"; + } + + private static final String GET_ALL_IMO_QUERY = + "SELECT imo_number FROM ship_data ORDER BY id"; +// "SELECT imo_number FROM snp_data.ship_data where imo_number > (select max(imo) from snp_data.t_berthcalls) ORDER BY imo_number"; + + @Override + protected void beforeFetch() { + // 전처리 과정 + // Step 1. IMO 전체 번호 조회 + /*log.info("[{}] ship_data 테이블에서 IMO 번호 조회 시작...", getReaderName()); + + allImoNumbers = jdbcTemplate.queryForList(GET_ALL_IMO_QUERY, String.class); + int totalBatches = (int) Math.ceil((double) allImoNumbers.size() / batchSize); + + log.info("[{}] 총 {} 개의 IMO 번호 조회 완료", getReaderName(), allImoNumbers.size()); + log.info("[{}] {}개씩 배치로 분할하여 API 호출 예정", getReaderName(), batchSize); + log.info("[{}] 예상 배치 수: {} 개", getReaderName(), totalBatches); + + // API 통계 초기화 + updateApiCallStats(totalBatches, 0);*/ + log.info("[PSC] 요청 날짜 범위: {} → {}", fromDate, toDate); + } + + + @Override + protected List fetchNextBatch() { + + + // 1) 처음 호출이면 API 한 번 호출해서 전체 데이터를 가져온다 + if (allData == null) { + log.info("[PSC] 최초 API 조회 실행: {} ~ {}", fromDate, toDate); + allData = callApiWithBatch(fromDate, toDate); + + if (allData == null || allData.isEmpty()) { + log.warn("[PSC] 조회된 데이터 없음 → 종료"); + return null; + } + + log.info("[PSC] 총 {}건 데이터 조회됨. batchSize = {}", allData.size(), batchSize); + } + + // 2) 이미 끝까지 읽었으면 종료 + if (currentBatchIndex >= allData.size()) { + log.info("[PSC] 모든 배치 처리 완료"); + return null; // Step 종료 신호 + } + + // 3) 이번 배치의 end 계산 + int end = Math.min(currentBatchIndex + batchSize, allData.size()); + + // 4) 현재 batch 리스트 잘라서 반환 + List batch = allData.subList(currentBatchIndex, end); + + int batchNum = (currentBatchIndex / batchSize) + 1; + int totalBatches = (int) Math.ceil((double) allData.size() / batchSize); + + log.info("[PSC] 배치 {}/{} 처리 중: {}건", batchNum, totalBatches, batch.size()); + + // 다음 batch 인덱스 이동 + currentBatchIndex = end; + + return batch; + } + + // private List callApiWithBatch(String lrno) { + private List callApiWithBatch(String from, String to) { + + String[] f = from.split("-"); + String[] t = to.split("-"); + + String url = getApiPath() + + "?shipsCategory=0" + + "&fromYear=" + f[0] + + "&fromMonth=" + f[1] + + "&fromDay=" + f[2] + + "&toYear=" + t[0] + + "&toMonth=" + t[1] + + "&toDay=" + t[2]; + + log.info("[PSC] API 호출 URL = {}", url); + + String json = webClient.get() + .uri(url) + .retrieve() + .bodyToMono(String.class) + .block(); + + if (json == null || json.isBlank()) { + log.warn("[PSC] API 응답 없음"); + return Collections.emptyList(); + } + + try { + ObjectMapper mapper = new ObjectMapper(); + PscApiResponseDto resp = mapper.readValue(json, PscApiResponseDto.class); + + if (resp.getInspections() == null) { + log.warn("[PSC] inspections 필드 없음"); + return Collections.emptyList(); + } + + return resp.getInspections(); + + } catch (Exception e) { + log.error("[PSC] JSON 파싱 실패: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + @Override + protected void afterFetch(List data) { + if (data == null) { + int totalBatches = (int) Math.ceil((double) allData.size() / batchSize); + log.info("[{}] 전체 {} 개 배치 처리 완료", getReaderName(), totalBatches); + log.info("[{}] 총 {} 개의 IMO 번호에 대한 API 호출 종료", + getReaderName(), allData.size()); + } + } +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscAllCertificateRepository.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscAllCertificateRepository.java new file mode 100644 index 0000000..5d4586a --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscAllCertificateRepository.java @@ -0,0 +1,9 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.jobs.pscInspection.batch.entity.PscAllCertificateEntity; + +import java.util.List; + +public interface PscAllCertificateRepository { + void saveAllCertificates(List certificates); +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscAllCertificateRepositoryImpl.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscAllCertificateRepositoryImpl.java new file mode 100644 index 0000000..28a8bbb --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscAllCertificateRepositoryImpl.java @@ -0,0 +1,146 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.common.batch.repository.BaseJdbcRepository; +import com.snp.batch.jobs.pscInspection.batch.entity.PscAllCertificateEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscCertificateEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.PreparedStatement; +import java.sql.Timestamp; +import java.util.List; + +@Slf4j +@Repository +public class PscAllCertificateRepositoryImpl extends BaseJdbcRepository + implements PscAllCertificateRepository { + public PscAllCertificateRepositoryImpl(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "new_snp.psc_all_certificate"; + } + + @Override + protected RowMapper getRowMapper() { + return null; + } + + @Override + protected String getEntityName() { + return "PscAllCertificate"; + } + + @Override + protected String extractId(PscAllCertificateEntity entity) { + return entity.getCertificateId(); + } + + @Override + public String getInsertSql() { + return """ + INSERT INTO new_snp.psc_all_certificate( + certificate_id, + type_id, + data_set_version, + inspection_id, + lrno, + certificate_title_code, + certificate_title, + issuing_authority_code, + issuing_authority, + class_soc_of_issuer, + other_issuing_authority, + issue_date, + expiry_date, + last_survey_date, + survey_authority_code, + survey_authority, + other_survey_authority, + latest_survey_place, + latest_survey_place_code, + survey_authority_type, + inspection_date, + inspected_by + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, \s + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + ?, ? + ) + ON CONFLICT (certificate_id) + DO UPDATE SET + type_id = EXCLUDED.type_id, + data_set_version = EXCLUDED.data_set_version, + inspection_id = EXCLUDED.inspection_id, + lrno = EXCLUDED.lrno, + certificate_title_code = EXCLUDED.certificate_title_code, + certificate_title = EXCLUDED.certificate_title, + issuing_authority_code = EXCLUDED.issuing_authority_code, + issuing_authority = EXCLUDED.issuing_authority, + class_soc_of_issuer = EXCLUDED.class_soc_of_issuer, + other_issuing_authority = EXCLUDED.other_issuing_authority, + issue_date = EXCLUDED.issue_date, + expiry_date = EXCLUDED.expiry_date, + last_survey_date = EXCLUDED.last_survey_date, + survey_authority_code = EXCLUDED.survey_authority_code, + survey_authority = EXCLUDED.survey_authority, + other_survey_authority = EXCLUDED.other_survey_authority, + latest_survey_place = EXCLUDED.latest_survey_place, + latest_survey_place_code = EXCLUDED.latest_survey_place_code, + survey_authority_type = EXCLUDED.survey_authority_type, + inspection_date = EXCLUDED.inspection_date, + inspected_by = EXCLUDED.inspected_by + """; + + } + + @Override + protected String getUpdateSql() { + return null; + } + + @Override + protected void setInsertParameters(PreparedStatement ps, PscAllCertificateEntity e) throws Exception { + int i = 1; + + ps.setString(i++, e.getCertificateId()); + ps.setString(i++, e.getTypeId()); + ps.setString(i++, e.getDataSetVersion()); + ps.setString(i++, e.getInspectionId()); + ps.setString(i++, e.getLrno()); + ps.setString(i++, e.getCertificateTitleCode()); + ps.setString(i++, e.getCertificateTitle()); + ps.setString(i++, e.getIssuingAuthorityCode()); + ps.setString(i++, e.getIssuingAuthority()); + ps.setString(i++, e.getClassSocOfIssuer()); + ps.setString(i++, e.getOtherIssuingAuthority()); + ps.setTimestamp(i++, e.getIssueDate() != null ? Timestamp.valueOf(e.getIssueDate()) : null); + ps.setTimestamp(i++, e.getExpiryDate() != null ? Timestamp.valueOf(e.getExpiryDate()) : null); + ps.setTimestamp(i++, e.getLastSurveyDate() != null ? Timestamp.valueOf(e.getLastSurveyDate()) : null); + ps.setString(i++, e.getSurveyAuthorityCode()); + ps.setString(i++, e.getSurveyAuthority()); + ps.setString(i++, e.getOtherSurveyAuthority()); + ps.setString(i++, e.getLatestSurveyPlace()); + ps.setString(i++, e.getLatestSurveyPlaceCode()); + ps.setString(i++, e.getSurveyAuthorityType()); + ps.setString(i++, e.getInspectionDate()); + ps.setString(i++, e.getInspectedBy()); + } + + @Override + protected void setUpdateParameters(PreparedStatement ps, PscAllCertificateEntity entity) throws Exception { + + } + + @Override + public void saveAllCertificates(List entities) { + if (entities == null || entities.isEmpty()) return; + log.info("PSC AllCertificates 저장 시작 = {}건", entities.size()); + batchInsert(entities); + } + +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscCertificateRepository.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscCertificateRepository.java new file mode 100644 index 0000000..97041e1 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscCertificateRepository.java @@ -0,0 +1,10 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.jobs.pscInspection.batch.entity.PscCertificateEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscDefectEntity; + +import java.util.List; + +public interface PscCertificateRepository { + void saveCertificates(List certificates); +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscCertificateRepositoryImpl.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscCertificateRepositoryImpl.java new file mode 100644 index 0000000..b7ac013 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscCertificateRepositoryImpl.java @@ -0,0 +1,139 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.common.batch.repository.BaseJdbcRepository; +import com.snp.batch.jobs.pscInspection.batch.entity.PscCertificateEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscDefectEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.PreparedStatement; +import java.sql.Timestamp; +import java.util.List; + +@Slf4j +@Repository +public class PscCertificateRepositoryImpl extends BaseJdbcRepository + implements PscCertificateRepository { + public PscCertificateRepositoryImpl(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "new_snp.psc_certificate"; + } + + @Override + protected RowMapper getRowMapper() { + return null; + } + + @Override + protected String getEntityName() { + return "PscCertificate"; + } + + @Override + protected String extractId(PscCertificateEntity entity) { + return entity.getCertificateId(); + } + + @Override + public String getInsertSql() { + return """ + INSERT INTO new_snp.psc_certificate( + certificate_id, + type_id, + data_set_version, + certificate_title, + certificate_title_code, + class_soc_of_issuer, + expiry_date, + inspection_id, + issue_date, + issuing_authority, + issuing_authority_code, + last_survey_date, + latest_survey_place, + latest_survey_place_code, + lrno, + other_issuing_authority, + other_survey_authority, + survey_authority, + survey_authority_code, + survey_authority_type + ) VALUES ( + ?,?,?,?,?,?,?,?,?,?, + ?,?,?,?,?,?,?,?,?,? + ) + ON CONFLICT (certificate_id) + DO UPDATE SET + type_id = EXCLUDED.type_id, + data_set_version = EXCLUDED.data_set_version, + certificate_title = EXCLUDED.certificate_title, + certificate_title_code = EXCLUDED.certificate_title_code, + class_soc_of_issuer = EXCLUDED.class_soc_of_issuer, + expiry_date = EXCLUDED.expiry_date, + inspection_id = EXCLUDED.inspection_id, + issue_date = EXCLUDED.issue_date, + issuing_authority = EXCLUDED.issuing_authority, + issuing_authority_code = EXCLUDED.issuing_authority_code, + last_survey_date = EXCLUDED.last_survey_date, + latest_survey_place = EXCLUDED.latest_survey_place, + latest_survey_place_code = EXCLUDED.latest_survey_place_code, + lrno = EXCLUDED.lrno, + other_issuing_authority = EXCLUDED.other_issuing_authority, + other_survey_authority = EXCLUDED.other_survey_authority, + survey_authority = EXCLUDED.survey_authority, + survey_authority_code = EXCLUDED.survey_authority_code, + survey_authority_type = EXCLUDED.survey_authority_type + """; + + } + + @Override + protected String getUpdateSql() { + return null; + } + + @Override + protected void setInsertParameters(PreparedStatement ps, PscCertificateEntity e) throws Exception { + int i = 1; + + ps.setString(i++, e.getCertificateId()); + ps.setString(i++, e.getTypeId()); + ps.setString(i++, e.getDataSetVersion()); + ps.setString(i++, e.getCertificateTitle()); + ps.setString(i++, e.getCertificateTitleCode()); + ps.setString(i++, e.getClassSocOfIssuer()); + ps.setTimestamp(i++, e.getExpiryDate() != null ? Timestamp.valueOf(e.getExpiryDate()) : null); + ps.setString(i++, e.getInspectionId()); + ps.setTimestamp(i++, e.getIssueDate() != null ? Timestamp.valueOf(e.getIssueDate()) : null); + ps.setString(i++, e.getIssuingAuthority()); + ps.setString(i++, e.getIssuingAuthorityCode()); + ps.setTimestamp(i++, e.getLastSurveyDate() != null ? Timestamp.valueOf(e.getLastSurveyDate()) : null); + ps.setString(i++, e.getLatestSurveyPlace()); + ps.setString(i++, e.getLatestSurveyPlaceCode()); + ps.setString(i++, e.getLrno()); + ps.setString(i++, e.getOtherIssuingAuthority()); + ps.setString(i++, e.getOtherSurveyAuthority()); + ps.setString(i++, e.getSurveyAuthority()); + ps.setString(i++, e.getSurveyAuthorityCode()); + ps.setString(i++, e.getSurveyAuthorityType()); + } + + @Override + protected void setUpdateParameters(PreparedStatement ps, PscCertificateEntity entity) throws Exception { + + } + + @Override + public void saveCertificates(List entities) { + if (entities == null || entities.isEmpty()) return; + log.info("PSC Certificate 저장 시작 = {}건", entities.size()); + batchInsert(entities); + } + +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscDefectRepository.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscDefectRepository.java new file mode 100644 index 0000000..35d9029 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscDefectRepository.java @@ -0,0 +1,10 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.jobs.pscInspection.batch.entity.PscDefectEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscInspectionEntity; + +import java.util.List; + +public interface PscDefectRepository { + void saveDefects(List defects); +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscDefectRepositoryImpl.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscDefectRepositoryImpl.java new file mode 100644 index 0000000..9fedb79 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscDefectRepositoryImpl.java @@ -0,0 +1,163 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.common.batch.repository.BaseJdbcRepository; +import com.snp.batch.jobs.pscInspection.batch.entity.PscDefectEntity; +import com.snp.batch.jobs.pscInspection.batch.entity.PscInspectionEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.PreparedStatement; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.List; + +@Slf4j +@Repository +public class PscDefectRepositoryImpl extends BaseJdbcRepository + implements PscDefectRepository { + public PscDefectRepositoryImpl(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "new_snp.psc_detail"; + } + + @Override + protected RowMapper getRowMapper() { + return null; + } + + @Override + protected String getEntityName() { + return "PscInspection"; + } + + @Override + protected String extractId(PscDefectEntity entity) { + return entity.getInspectionId(); + } + + @Override + public String getInsertSql() { + return """ + INSERT INTO new_snp.psc_defect( + defect_id, + inspection_id, + type_id, + data_set_version, + action_1, + action_2, + action_3, + action_code_1, + action_code_2, + action_code_3, + amsa_action_code_1, + amsa_action_code_2, + amsa_action_code_3, + class_is_responsible, + defect_code, + defect_text, + defective_item_code, + detention_reason_deficiency, + main_defect_code, + main_defect_text, + nature_of_defect_code, + nature_of_defect_decode, + other_action, + other_recognised_org_resp, + recognised_org_resp, + recognised_org_resp_code, + recognised_org_resp_yn, + is_accidental_damage + ) VALUES (?,?,?,?,?,?,?,?,?,?, + ?,?,?,?,?,?,?,?,?,?, + ?,?,?,?,?,?,?,?) + ON CONFLICT (defect_id) + DO UPDATE SET + inspection_id = EXCLUDED.inspection_id, + type_id = EXCLUDED.type_id, + data_set_version = EXCLUDED.data_set_version, + action_1 = EXCLUDED.action_1, + action_2 = EXCLUDED.action_2, + action_3 = EXCLUDED.action_3, + action_code_1 = EXCLUDED.action_code_1, + action_code_2 = EXCLUDED.action_code_2, + action_code_3 = EXCLUDED.action_code_3, + amsa_action_code_1 = EXCLUDED.amsa_action_code_1, + amsa_action_code_2 = EXCLUDED.amsa_action_code_2, + amsa_action_code_3 = EXCLUDED.amsa_action_code_3, + class_is_responsible = EXCLUDED.class_is_responsible, + defect_code = EXCLUDED.defect_code, + defect_text = EXCLUDED.defect_text, + defective_item_code = EXCLUDED.defective_item_code, + detention_reason_deficiency = EXCLUDED.detention_reason_deficiency, + main_defect_code = EXCLUDED.main_defect_code, + main_defect_text = EXCLUDED.main_defect_text, + nature_of_defect_code = EXCLUDED.nature_of_defect_code, + nature_of_defect_decode = EXCLUDED.nature_of_defect_decode, + other_action = EXCLUDED.other_action, + other_recognised_org_resp = EXCLUDED.other_recognised_org_resp, + recognised_org_resp = EXCLUDED.recognised_org_resp, + recognised_org_resp_code = EXCLUDED.recognised_org_resp_code, + recognised_org_resp_yn = EXCLUDED.recognised_org_resp_yn, + is_accidental_damage = EXCLUDED.is_accidental_damage + """; + + } + + @Override + protected String getUpdateSql() { + return null; + } + + @Override + protected void setInsertParameters(PreparedStatement ps, PscDefectEntity e) throws Exception { + int i = 1; + + ps.setString(i++, e.getDefectId()); + ps.setString(i++, e.getInspectionId()); + ps.setString(i++, e.getTypeId()); + ps.setString(i++, e.getDataSetVersion()); + ps.setString(i++, e.getAction1()); + ps.setString(i++, e.getAction2()); + ps.setString(i++, e.getAction3()); + ps.setString(i++, e.getActionCode1()); + ps.setString(i++, e.getActionCode2()); + ps.setString(i++, e.getActionCode3()); + ps.setString(i++, e.getAmsaActionCode1()); + ps.setString(i++, e.getAmsaActionCode2()); + ps.setString(i++, e.getAmsaActionCode3()); + ps.setString(i++, e.getClassIsResponsible()); + ps.setString(i++, e.getDefectCode()); + ps.setString(i++, e.getDefectText()); + ps.setString(i++, e.getDefectiveItemCode()); + ps.setString(i++, e.getDetentionReasonDeficiency()); + ps.setString(i++, e.getMainDefectCode()); + ps.setString(i++, e.getMainDefectText()); + ps.setString(i++, e.getNatureOfDefectCode()); + ps.setString(i++, e.getNatureOfDefectDecode()); + ps.setString(i++, e.getOtherAction()); + ps.setString(i++, e.getOtherRecognisedOrgResp()); + ps.setString(i++, e.getRecognisedOrgResp()); + ps.setString(i++, e.getRecognisedOrgRespCode()); + ps.setString(i++, e.getRecognisedOrgRespYn()); + ps.setString(i++, e.getIsAccidentalDamage()); + } + + @Override + protected void setUpdateParameters(PreparedStatement ps, PscDefectEntity entity) throws Exception { + + } + + @Override + public void saveDefects(List entities) { + if (entities == null || entities.isEmpty()) return; + log.info("PSC Defect 저장 시작 = {}건", entities.size()); + batchInsert(entities); + } + +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscInspectionRepository.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscInspectionRepository.java new file mode 100644 index 0000000..201f6c7 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscInspectionRepository.java @@ -0,0 +1,9 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.jobs.pscInspection.batch.entity.PscInspectionEntity; + +import java.util.List; + +public interface PscInspectionRepository { + void saveAll(List entities); +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscInspectionRepositoryImpl.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscInspectionRepositoryImpl.java new file mode 100644 index 0000000..e558071 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/repository/PscInspectionRepositoryImpl.java @@ -0,0 +1,186 @@ +package com.snp.batch.jobs.pscInspection.batch.repository; + +import com.snp.batch.common.batch.repository.BaseJdbcRepository; +import com.snp.batch.jobs.pscInspection.batch.entity.PscInspectionEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import java.sql.PreparedStatement; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.List; + +@Slf4j +@Repository +public class PscInspectionRepositoryImpl extends BaseJdbcRepository + implements PscInspectionRepository{ + public PscInspectionRepositoryImpl(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "new_snp.psc_detail"; + } + + @Override + protected String getEntityName() { + return "PscInspection"; + } + + @Override + protected String extractId(PscInspectionEntity entity) { + return entity.getInspectionId(); + } + + @Override + public String getInsertSql() { + return """ + INSERT INTO new_snp.psc_detail( + inspection_id, + type_id, + data_set_version, + authorisation, + call_sign, + class, + cargo, + charterer, + country, + inspection_date, + release_date, + ship_detained, + dead_weight, + expanded_inspection, + flag, + follow_up_inspection, + gross_tonnage, + inspection_port_code, + inspection_port_decode, + keel_laid, + last_updated, + ihslr_or_imo_ship_no, + manager, + number_of_days_detained, + number_of_defects, + number_of_part_days_detained, + other_inspection_type, + owner, + ship_name, + ship_type_code, + ship_type_decode, + source, + unlocode, + year_of_build + ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + ?, ?, ?, ? + ) + ON CONFLICT (inspection_id) + DO UPDATE SET + type_id = EXCLUDED.type_id, + data_set_version = EXCLUDED.data_set_version, + authorisation = EXCLUDED.authorisation, + call_sign = EXCLUDED.call_sign, + class = EXCLUDED.class, + cargo = EXCLUDED.cargo, + charterer = EXCLUDED.charterer, + country = EXCLUDED.country, + inspection_date = EXCLUDED.inspection_date, + release_date = EXCLUDED.release_date, + ship_detained = EXCLUDED.ship_detained, + dead_weight = EXCLUDED.dead_weight, + expanded_inspection = EXCLUDED.expanded_inspection, + flag = EXCLUDED.flag, + follow_up_inspection = EXCLUDED.follow_up_inspection, + gross_tonnage = EXCLUDED.gross_tonnage, + inspection_port_code = EXCLUDED.inspection_port_code, + inspection_port_decode = EXCLUDED.inspection_port_decode, + keel_laid = EXCLUDED.keel_laid, + last_updated = EXCLUDED.last_updated, + ihslr_or_imo_ship_no = EXCLUDED.ihslr_or_imo_ship_no, + manager = EXCLUDED.manager, + number_of_days_detained = EXCLUDED.number_of_days_detained, + number_of_defects = EXCLUDED.number_of_defects, + number_of_part_days_detained = EXCLUDED.number_of_part_days_detained, + other_inspection_type = EXCLUDED.other_inspection_type, + owner = EXCLUDED.owner, + ship_name = EXCLUDED.ship_name, + ship_type_code = EXCLUDED.ship_type_code, + ship_type_decode = EXCLUDED.ship_type_decode, + source = EXCLUDED.source, + unlocode = EXCLUDED.unlocode, + year_of_build = EXCLUDED.year_of_build + """; + + } + + @Override + protected void setInsertParameters(PreparedStatement ps, PscInspectionEntity e) throws Exception { + int i = 1; + + ps.setString(i++, e.getInspectionId()); + ps.setString(i++, e.getTypeId()); + ps.setString(i++, e.getDataSetVersion()); + ps.setString(i++, e.getAuthorisation()); + ps.setString(i++, e.getCallSign()); + ps.setString(i++, e.getShipClass()); + ps.setString(i++, e.getCargo()); + ps.setString(i++, e.getCharterer()); + ps.setString(i++, e.getCountry()); + ps.setTimestamp(i++, e.getInspectionDate() != null ? Timestamp.valueOf(e.getInspectionDate()) : null); + ps.setTimestamp(i++, e.getReleaseDate() != null ? Timestamp.valueOf(e.getReleaseDate()) : null); + ps.setString(i++, e.getShipDetained()); + ps.setString(i++, e.getDeadWeight()); + ps.setString(i++, e.getExpandedInspection()); + ps.setString(i++, e.getFlag()); + ps.setString(i++, e.getFollowUpInspection()); + ps.setString(i++, e.getGrossTonnage()); + ps.setString(i++, e.getInspectionPortCode()); + ps.setString(i++, e.getInspectionPortDecode()); + ps.setString(i++, e.getKeelLaid()); + ps.setTimestamp(i++, e.getLastUpdated() != null ? Timestamp.valueOf(e.getLastUpdated()) : null); + ps.setString(i++, e.getIhslrOrImoShipNo()); + ps.setString(i++, e.getManager()); + if (e.getNumberOfDaysDetained() != null) { + ps.setInt(i++, e.getNumberOfDaysDetained()); + } else { + ps.setNull(i++, Types.INTEGER); + } + ps.setString(i++, e.getNumberOfDefects()); + ps.setBigDecimal(i++, e.getNumberOfPartDaysDetained()); + ps.setString(i++, e.getOtherInspectionType()); + ps.setString(i++, e.getOwner()); + ps.setString(i++, e.getShipName()); + ps.setString(i++, e.getShipTypeCode()); + ps.setString(i++, e.getShipTypeDecode()); + ps.setString(i++, e.getSource()); + ps.setString(i++, e.getUnlocode()); + ps.setString(i++, e.getYearOfBuild()); + } + + @Override + public void saveAll(List entities) { + if (entities == null || entities.isEmpty()) return; + log.info("PSC Inspection 저장 시작 = {}건", entities.size()); + batchInsert(entities); + } + + @Override + protected void setUpdateParameters(PreparedStatement ps, PscInspectionEntity entity) throws Exception { + + } + + @Override + protected String getUpdateSql() { + return null; + } + + @Override + protected RowMapper getRowMapper() { + return null; + } +} diff --git a/src/main/java/com/snp/batch/jobs/pscInspection/batch/writer/PscInspectionWriter.java b/src/main/java/com/snp/batch/jobs/pscInspection/batch/writer/PscInspectionWriter.java new file mode 100644 index 0000000..725d167 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/pscInspection/batch/writer/PscInspectionWriter.java @@ -0,0 +1,55 @@ +package com.snp.batch.jobs.pscInspection.batch.writer; + +import com.snp.batch.common.batch.writer.BaseWriter; +import com.snp.batch.jobs.pscInspection.batch.entity.PscInspectionEntity; +import com.snp.batch.jobs.pscInspection.batch.repository.PscAllCertificateRepository; +import com.snp.batch.jobs.pscInspection.batch.repository.PscCertificateRepository; +import com.snp.batch.jobs.pscInspection.batch.repository.PscDefectRepository; +import com.snp.batch.jobs.pscInspection.batch.repository.PscInspectionRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class PscInspectionWriter extends BaseWriter { + private final PscInspectionRepository pscInspectionRepository; + private final PscDefectRepository pscDefectRepository; + private final PscCertificateRepository pscCertificateRepository; + private final PscAllCertificateRepository pscAllCertificateRepository; + + public PscInspectionWriter(PscInspectionRepository pscInspectionRepository, + PscDefectRepository pscDefectRepository, + PscCertificateRepository pscCertificateRepository, + PscAllCertificateRepository pscAllCertificateRepository) { + super("PscInspection"); + this.pscInspectionRepository = pscInspectionRepository; + this.pscDefectRepository = pscDefectRepository; + this.pscCertificateRepository = pscCertificateRepository; + this.pscAllCertificateRepository = pscAllCertificateRepository; + } + + @Override + protected void writeItems(List items) throws Exception { + + if (items == null || items.isEmpty()) return; + //pscInspectionRepository.saveAll(items); + log.info("PSC Inspection 저장: {} 건", items.size()); + + for (PscInspectionEntity entity : items) { + pscInspectionRepository.saveAll(List.of(entity)); + pscDefectRepository.saveDefects(entity.getDefects()); + pscCertificateRepository.saveCertificates(entity.getCertificates()); + pscAllCertificateRepository.saveAllCertificates(entity.getAllCertificates()); + + // 효율적으로 로그 + int defectCount = entity.getDefects() != null ? entity.getDefects().size() : 0; + int certificateCount = entity.getCertificates() != null ? entity.getCertificates().size() : 0; + int allCertificateCount = entity.getAllCertificates() != null ? entity.getAllCertificates().size() : 0; + + log.info("Inspection ID: {}, Defects: {}, Certificates: {}, AllCertificates: {}", + entity.getInspectionId(), defectCount, certificateCount, allCertificateCount); + } + } +} diff --git a/src/main/java/com/snp/batch/jobs/shipMovementBerthCalls/batch/reader/BerthCallsReader.java b/src/main/java/com/snp/batch/jobs/shipMovementBerthCalls/batch/reader/BerthCallsReader.java index 9f6771d..38c8d8f 100644 --- a/src/main/java/com/snp/batch/jobs/shipMovementBerthCalls/batch/reader/BerthCallsReader.java +++ b/src/main/java/com/snp/batch/jobs/shipMovementBerthCalls/batch/reader/BerthCallsReader.java @@ -82,7 +82,7 @@ public class BerthCallsReader extends BaseApiReader { private static final String GET_ALL_IMO_QUERY = // "SELECT imo_number FROM ship_data ORDER BY id"; - "SELECT imo_number FROM snp_data.ship_data where imo_number > (select max(imo) from snp_data.t_berthcalls) ORDER BY imo_number"; + "SELECT imo_number FROM snp_data.ship_data where imo_number > (select max(imo) from snp_data.t_berthcall) ORDER BY imo_number"; /** * 최초 1회만 실행: ship_data 테이블에서 IMO 번호 전체 조회