From 43057d74fb380e761d3de7845993f2e6826ac240 Mon Sep 17 00:00:00 2001 From: hyojin kim Date: Fri, 16 Jan 2026 14:15:00 +0900 Subject: [PATCH] =?UTF-8?q?:sparkles:=20Company=20Detail=20=EC=88=98?= =?UTF-8?q?=EC=A7=91=20=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../batch/common/util/JsonChangeDetector.java | 3 +- .../config/ShipDetailUpdateJobConfig.java | 2 +- .../batch/dto/CompanyDetailDto.java | 82 ++++++ .../shipdetail/batch/dto/ShipDetailDto.java | 20 +- .../batch/dto/ShipDetailUpdate.java | 1 + .../batch/entity/CompanyDetailEntity.java | 46 ++++ .../batch/entity/ShipDetailEntity.java | 9 + .../processor/ShipDetailDataProcessor.java | 59 ++++ .../reader/ShipDetailUpdateDataReader.java | 2 +- .../repository/ShipDetailRepository.java | 2 + .../repository/ShipDetailRepositoryImpl.java | 257 ++++++++++++------ .../batch/repository/ShipDetailSql.java | 51 ++++ .../batch/writer/ShipDetailDataWriter.java | 5 + 13 files changed, 454 insertions(+), 85 deletions(-) create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/dto/CompanyDetailDto.java create mode 100644 src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CompanyDetailEntity.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 a8630ac..41eaf65 100644 --- a/src/main/java/com/snp/batch/common/util/JsonChangeDetector.java +++ b/src/main/java/com/snp/batch/common/util/JsonChangeDetector.java @@ -13,7 +13,7 @@ public class JsonChangeDetector { // 해시 비교에서 제외할 필드 목록 (DataSetVersion 등) // 이 목록은 모든 JSON 계층에 걸쳐 적용됩니다. private static final java.util.Set EXCLUDE_KEYS = - java.util.Set.of("DataSetVersion", "APSStatus", "LastUpdateDate", "LastUpdateDateTime"); + java.util.Set.of("DataSetVersion", "APSStatus", "LastUpdateDateTime"); // ========================================================================= // ✅ LIST_SORT_KEYS: 정적 초기화 블록을 사용한 Map 정의 @@ -47,6 +47,7 @@ public class JsonChangeDetector { map.put("DarkActivityConfirmed", "Lrno,Mmsi,Dark_Time,Dark_Status"); map.put("CompanyComplianceDetails", "OwCode"); map.put("CompanyVesselRelationships", "LRNO"); + map.put("CompanyDetailsComplexWithCodesAndParent", "OWCODE,LastChangeDate"); LIST_SORT_KEYS = Collections.unmodifiableMap(map); } 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 index a4a15d7..e3c56f7 100644 --- 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 @@ -117,7 +117,7 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig darkActivityConfirmed; + @JsonProperty("CompanyDetailsComplexWithCodesAndParent") + private List companyDetailDtoList; + } 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 7081936..e752538 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 @@ -46,4 +46,5 @@ public class ShipDetailUpdate { private final List darkActivityConfirmedEntityList; private final List companyComplianceEntityList; private final List companyVesselRelationshipEntityList; + private final List companyDetailEntityList; } \ No newline at end of file diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CompanyDetailEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CompanyDetailEntity.java new file mode 100644 index 0000000..98ecd3a --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/CompanyDetailEntity.java @@ -0,0 +1,46 @@ +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 CompanyDetailEntity extends BaseEntity { + private String dataSetVersion; + private String owcode; + private String shortcompanyname; + private String countryname; + private String townname; + private String telephone; + private String telex; + private String emailaddress; + private String website; + private String fullname; + private String careofcode; + private String roomfloorbuilding1; + private String roomfloorbuilding2; + private String roomfloorbuilding3; + private String pobox; + private String streetnumber; + private String street; + private String prepostcode; + private String postpostcode; + private String nationalityofregistration; + private String nationalityofcontrol; + private String locationcode; + private String nationalityofregistrationcode; + private String nationalityofcontrolcode; + private String lastchangedate; + private String parentcompany; + private String companystatus; + private String fulladdress; + private String facsimile; + private String foundeddate; +} diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipDetailEntity.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipDetailEntity.java index 297c886..aa7c9d2 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipDetailEntity.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/entity/ShipDetailEntity.java @@ -269,4 +269,13 @@ public class ShipDetailEntity extends BaseEntity { private String auxiliarygeneratorsdescriptivenarrative; private String bunkersdescriptivenarrative; + // 마지막 수정 일자 + private String lastUpdateDate; + // 회사 코드 + private String documentOfComplianceDOCCompanyCode; + private String groupBeneficialOwnerCompanyCode; + private String operatorCompanyCode; + private String shipManagerCompanyCode; + private String technicalManagerCode; + private String registeredOwnerCode; } 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 25a9fb7..fb99182 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 @@ -62,6 +62,7 @@ public class ShipDetailDataProcessor extends BaseProcessor darkActivityConfirmedEntityList = makeDarkActivityConfirmedEntityList(comparisonData.getStructuredDto().getDarkActivityConfirmed()); List companyComplianceEntityList = makeCompanyComplianceEntityList(comparisonData.getStructuredDto().getCompanyComplianceDetails()); List companyVesselRelationshipEntityList = makeCompanyVesselRelationshipEntityList(comparisonData.getStructuredDto().getCompanyVesselRelationships()); + List companyDetailEntityList = makeCompanyDetailEntity(comparisonData.getStructuredDto().getCompanyDetailDtoList()); // 3. 최종 업데이트 DTO 생성 (Writer에 전달) return ShipDetailUpdate.builder() @@ -94,6 +95,7 @@ public class ShipDetailDataProcessor extends BaseProcessor makeCompanyDetailEntity(List dtoList) { + List companyDetailEntityList = new ArrayList<>(); + + if (dtoList == null || dtoList.isEmpty()) { + return companyDetailEntityList; + } + + for (CompanyDetailDto dto : dtoList) { + String datasetVersion = (dto.getDataSetVersion() != null) ? dto.getDataSetVersion().getDataSetVersion() : null; + + CompanyDetailEntity entity = CompanyDetailEntity.builder() + .dataSetVersion(safeGetString(datasetVersion)) + .owcode(safeGetString(dto.getOwcode())) + .shortcompanyname(safeGetString(dto.getShortCompanyName())) + .countryname(safeGetString(dto.getCountryName())) + .townname(safeGetString(dto.getTownName())) + .telephone(safeGetString(dto.getTelephone())) + .telex(safeGetString(dto.getTelex())) + .emailaddress(safeGetString(dto.getEmailaddress())) + .website(safeGetString(dto.getWebsite())) + .fullname(safeGetString(dto.getFullName())) + .careofcode(safeGetString(dto.getCareOfCode())) + .roomfloorbuilding1(safeGetString(dto.getRoomFloorBuilding1())) + .roomfloorbuilding2(safeGetString(dto.getRoomFloorBuilding2())) + .roomfloorbuilding3(safeGetString(dto.getRoomFloorBuilding3())) + .pobox(safeGetString(dto.getPoBox())) + .streetnumber(safeGetString(dto.getStreetNumber())) + .street(safeGetString(dto.getStreet())) + .prepostcode(safeGetString(dto.getPrePostcode())) + .postpostcode(safeGetString(dto.getPostPostcode())) + .nationalityofregistration(safeGetString(dto.getNationalityofRegistration())) + .nationalityofcontrol(safeGetString(dto.getNationalityofControl())) + .locationcode(safeGetString(dto.getLocationCode())) + .nationalityofregistrationcode(safeGetString(dto.getNationalityofRegistrationCode())) + .nationalityofcontrolcode(safeGetString(dto.getNationalityofControlCode())) + .lastchangedate(safeGetString(dto.getLastChangeDate())) + .parentcompany(safeGetString(dto.getParentCompany())) + .companystatus(safeGetString(dto.getCompanyStatus())) + .fulladdress(safeGetString(dto.getFullAddress())) + .facsimile(safeGetString(dto.getFacsimile())) + .foundeddate(safeGetString(dto.getFoundedDate())) + .build(); + + companyDetailEntityList.add(entity); + } + return companyDetailEntityList; + } + /** * 해시값을 비교하여 변경 여부를 판단합니다. */ 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 index 1e882b3..34cc868 100644 --- 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 @@ -29,7 +29,7 @@ public class ShipDetailUpdateDataReader extends BaseApiReader dbMasterHashes; private int currentBatchIndex = 0; - private final int batchSize = 50; + private final int batchSize = 5; public ShipDetailUpdateDataReader(WebClient webClient, JdbcTemplate jdbcTemplate, ObjectMapper objectMapper,BatchDateService batchDateService, BatchApiLogService batchApiLogService, String maritimeApiUrl) { super(webClient); this.jdbcTemplate = jdbcTemplate; 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 e85ef7a..588615d 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 @@ -63,6 +63,8 @@ public interface ShipDetailRepository { void saveAllCompanyVesselRelationshipData(List entities); + void saveAllCompanyDetailData(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 b8cdab4..df18ae6 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 @@ -61,7 +61,9 @@ public class ShipDetailRepositoryImpl extends BaseJdbcRepository entities) { + String entityName = "CompanyDetailEntity"; + String sql = ShipDetailSql.getCompanyDetailSql(); + + if (entities == null || entities.isEmpty()) { + return; + } + + log.debug("{} 배치 삽입 시작: {} 건", entityName, entities.size()); + + jdbcTemplate.batchUpdate(sql, entities, entities.size(), + (ps, entity) -> { + try { + setCompanyDetailInsertParameters(ps, entity); + } catch (Exception e) { + log.error("배치 삽입 파라미터 설정 실패 - " + entityName, e); + throw new RuntimeException(e); + } + }); + + log.info("{} 배치 삽입 완료: {} 건", entityName, entities.size()); + } + + private void setCompanyDetailInsertParameters(PreparedStatement ps, CompanyDetailEntity entity) throws Exception { + int idx = 1; + ps.setString(idx++, entity.getDataSetVersion()); + ps.setString(idx++, entity.getOwcode()); + ps.setString(idx++, entity.getShortcompanyname()); + ps.setString(idx++, entity.getCountryname()); + ps.setString(idx++, entity.getTownname()); + ps.setString(idx++, entity.getTelephone()); + ps.setString(idx++, entity.getTelex()); + ps.setString(idx++, entity.getEmailaddress()); + ps.setString(idx++, entity.getWebsite()); + ps.setString(idx++, entity.getFullname()); + ps.setString(idx++, entity.getCareofcode()); + ps.setString(idx++, entity.getRoomfloorbuilding1()); + ps.setString(idx++, entity.getRoomfloorbuilding2()); + ps.setString(idx++, entity.getRoomfloorbuilding3()); + ps.setString(idx++, entity.getPobox()); + ps.setString(idx++, entity.getStreetnumber()); + ps.setString(idx++, entity.getStreet()); + ps.setString(idx++, entity.getPrepostcode()); + ps.setString(idx++, entity.getPostpostcode()); + ps.setString(idx++, entity.getNationalityofregistration()); + ps.setString(idx++, entity.getNationalityofcontrol()); + ps.setString(idx++, entity.getLocationcode()); + ps.setString(idx++, entity.getNationalityofregistrationcode()); + ps.setString(idx++, entity.getNationalityofcontrolcode()); + ps.setString(idx++, entity.getLastchangedate()); + ps.setString(idx++, entity.getParentcompany()); + ps.setString(idx++, entity.getCompanystatus()); + ps.setString(idx++, entity.getFulladdress()); + ps.setString(idx++, entity.getFacsimile()); + ps.setString(idx++, entity.getFoundeddate()); + } + 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); diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailSql.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailSql.java index 30a3d1f..9506985 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailSql.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/repository/ShipDetailSql.java @@ -614,4 +614,55 @@ public class ShipDetailSql { """; } + public static String getCompanyDetailSql() { + return """ + INSERT INTO snp_data.tb_company_detail ( + datasetversion, owcode, shortcompanyname, countryname, townname, + telephone, telex, emailaddress, website, fullname, + careofcode, roomfloorbuilding1, roomfloorbuilding2, roomfloorbuilding3, pobox, + streetnumber, street, prepostcode, postpostcode, nationalityofregistration, + nationalityofcontrol, locationcode, nationalityofregistrationcode, nationalityofcontrolcode, lastchangedate, + parentcompany, companystatus, fulladdress, facsimile, foundeddate + ) VALUES ( + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ?, + ?, ?, ?, ?, ? + ) + ON CONFLICT (owcode) DO UPDATE SET + datasetversion = EXCLUDED.datasetversion, + shortcompanyname = EXCLUDED.shortcompanyname, + countryname = EXCLUDED.countryname, + townname = EXCLUDED.townname, + telephone = EXCLUDED.telephone, + telex = EXCLUDED.telex, + emailaddress = EXCLUDED.emailaddress, + website = EXCLUDED.website, + fullname = EXCLUDED.fullname, + careofcode = EXCLUDED.careofcode, + roomfloorbuilding1 = EXCLUDED.roomfloorbuilding1, + roomfloorbuilding2 = EXCLUDED.roomfloorbuilding2, + roomfloorbuilding3 = EXCLUDED.roomfloorbuilding3, + pobox = EXCLUDED.pobox, + streetnumber = EXCLUDED.streetnumber, + street = EXCLUDED.street, + prepostcode = EXCLUDED.prepostcode, + postpostcode = EXCLUDED.postpostcode, + nationalityofregistration = EXCLUDED.nationalityofregistration, + nationalityofcontrol = EXCLUDED.nationalityofcontrol, + locationcode = EXCLUDED.locationcode, + nationalityofregistrationcode = EXCLUDED.nationalityofregistrationcode, + nationalityofcontrolcode = EXCLUDED.nationalityofcontrolcode, + lastchangedate = EXCLUDED.lastchangedate, + parentcompany = EXCLUDED.parentcompany, + companystatus = EXCLUDED.companystatus, + fulladdress = EXCLUDED.fulladdress, + facsimile = EXCLUDED.facsimile, + foundeddate = EXCLUDED.foundeddate, + batch_flag = 'N'; + """; + } + } diff --git a/src/main/java/com/snp/batch/jobs/shipdetail/batch/writer/ShipDetailDataWriter.java b/src/main/java/com/snp/batch/jobs/shipdetail/batch/writer/ShipDetailDataWriter.java index cf39058..6f387b0 100644 --- a/src/main/java/com/snp/batch/jobs/shipdetail/batch/writer/ShipDetailDataWriter.java +++ b/src/main/java/com/snp/batch/jobs/shipdetail/batch/writer/ShipDetailDataWriter.java @@ -68,6 +68,7 @@ public class ShipDetailDataWriter extends BaseWriter { List darkActivityConfirmedEntities = flattenEntities(items, ShipDetailUpdate::getDarkActivityConfirmedEntityList); List companyComplianceEntities = flattenEntities(items, ShipDetailUpdate::getCompanyComplianceEntityList); List companyVesselRelationshipEntities = flattenEntities(items, ShipDetailUpdate::getCompanyVesselRelationshipEntityList); + List companyDetailEntities = flattenEntities(items, ShipDetailUpdate::getCompanyDetailEntityList); // 1-3. List (Hash값 데이터 처리용) List hashEntities = items.stream() @@ -180,6 +181,10 @@ public class ShipDetailDataWriter extends BaseWriter { log.debug("CompanyVesselRelationship 저장 시작: {} 건", companyVesselRelationshipEntities.size()); shipDetailRepository.saveAllCompanyVesselRelationshipData(companyVesselRelationshipEntities); + // CompanyVesselRelationship 저장 + log.debug("Company Detail 저장 시작: {} 건", companyDetailEntities.size()); + shipDetailRepository.saveAllCompanyDetailData(companyDetailEntities); + // ✅ 2-3. ShipHashRepository (Hash값 데이터) log.debug("Ship Hash 데이터 저장 시작: {} 건", hashEntities.size()); shipHashRepository.saveAllData(hashEntities);