Geojson File Data Type Change to Apply - Daniel C No.11

This commit is contained in:
DanielLee
2025-12-03 17:11:09 +09:00
parent 46de8c223a
commit 527167611b
4 changed files with 109 additions and 38 deletions

View File

@@ -22,8 +22,8 @@ public class GeoJsonMonitorConfig {
/** 처리 실패 파일을 이동할 폴더 경로 */ /** 처리 실패 파일을 이동할 폴더 경로 */
private String errorDirectory = "~/geojson/error"; private String errorDirectory = "~/geojson/error";
/** 파일 모니터링 스케줄 (cron 표현식) 기본값: 매 30초마다 실행 */ /** 파일 모니터링 스케줄 (cron 표현식) 기본값: 매 1분마다 실행 */
private String cronExpression = "0/30 * * * * *"; private String cronExpression = "0 * * * * *";
/** 지원하는 압축파일 확장자 */ /** 지원하는 압축파일 확장자 */
private String[] supportedExtensions = {"zip", "tar", "tar.gz", "tgz"}; private String[] supportedExtensions = {"zip", "tar", "tar.gz", "tgz"};

View File

@@ -27,43 +27,57 @@ public class GeoJsonDataService {
private final GeoJsonReader geoJsonReader = new GeoJsonReader(); private final GeoJsonReader geoJsonReader = new GeoJsonReader();
/** GeoJSON 파일들을 데이터베이스에 저장 */ /** GeoJSON 파일들을 데이터베이스에 저장 */
@Transactional
public List<Long> processGeoJsonFiles( public List<Long> processGeoJsonFiles(
Map<String, String> geoJsonContents, String archiveFileName) { Map<String, String> geoJsonContents, String archiveFileName) {
List<Long> savedIds = new ArrayList<>(); List<Long> savedIds = new ArrayList<>();
log.info("GeoJSON 파일 처리 시작: {} ({}개 파일)", archiveFileName, geoJsonContents.size()); log.info("GeoJSON 파일 처리 시작: {} ({}개 파일)", archiveFileName, geoJsonContents.size());
// 개별 파일별로 독립적인 트랜잭션으로 처리
for (Map.Entry<String, String> entry : geoJsonContents.entrySet()) { for (Map.Entry<String, String> entry : geoJsonContents.entrySet()) {
String fileName = entry.getKey(); String fileName = entry.getKey();
String geoJsonContent = entry.getValue(); String geoJsonContent = entry.getValue();
try { try {
Long savedId = processGeoJsonFile(fileName, geoJsonContent, archiveFileName); Long savedId = processGeoJsonFileWithTransaction(fileName, geoJsonContent, archiveFileName);
if (savedId != null) { if (savedId != null) {
savedIds.add(savedId); savedIds.add(savedId);
log.debug("GeoJSON 파일 저장 성공: {} (ID: {})", fileName, savedId); log.debug("GeoJSON 파일 저장 성공: {} (ID: {})", fileName, savedId);
// 학습 모델 결과 파일인지 확인하여 geometry 데이터 처리
if (isLearningModelResult(fileName, geoJsonContent)) {
processLearningModelGeometry(savedId, geoJsonContent, fileName);
}
} }
} catch (Exception e) { } catch (Exception e) {
log.error("GeoJSON 파일 처리 실패: {}", fileName, e); log.error("GeoJSON 파일 처리 실패: {}", fileName, e);
// 개별 파일 처리 실패는 전체 처리를 중단시키지 않음 // 개별 파일 처리 실패는 전체 처리를 중단시키지 않음
} }
// 메모리 정리
System.gc();
} }
log.info( log.info(
"GeoJSON 파일 처리 완료: {} (성공: {}개, 전체: {}개)", "GeoJSON 파일 처리 완료: {} (성공: {}개, 전체: {}개)",
archiveFileName, archiveFileName,
savedIds.size(), savedIds.size(),
geoJsonContents.size()); geoJsonContents.size());
return savedIds; return savedIds;
} }
/** 개별 파일을 별도 트랜잭션으로 처리 */
@Transactional
public Long processGeoJsonFileWithTransaction(
String fileName, String geoJsonContent, String archiveFileName) {
try {
Long savedId = processGeoJsonFile(fileName, geoJsonContent, archiveFileName);
if (savedId != null && isLearningModelResult(fileName, geoJsonContent)) {
processLearningModelGeometryOptimized(savedId, geoJsonContent, fileName);
}
return savedId;
} catch (Exception e) {
log.error("파일 처리 중 트랜잭션 에러: {}", fileName, e);
throw e; // 트랜잭션 롤백을 위해 재throw
}
}
/** 개별 GeoJSON 파일을 MapSheetLearnDataEntity로 변환하여 저장 */ /** 개별 GeoJSON 파일을 MapSheetLearnDataEntity로 변환하여 저장 */
private Long processGeoJsonFile(String fileName, String geoJsonContent, String archiveFileName) { private Long processGeoJsonFile(String fileName, String geoJsonContent, String archiveFileName) {
try { try {
@@ -74,7 +88,7 @@ public class GeoJsonDataService {
// 파일이 이미 처리되었는지 확인 // 파일이 이미 처리되었는지 확인
String dataPath = generateDataPath(archiveFileName, fileName); String dataPath = generateDataPath(archiveFileName, fileName);
Optional<MapSheetLearnDataEntity> existingData = Optional<MapSheetLearnDataEntity> existingData =
mapSheetLearnDataRepository.findByDataPath(dataPath); mapSheetLearnDataRepository.findByDataPath(dataPath);
if (existingData.isPresent()) { if (existingData.isPresent()) {
log.warn("이미 처리된 파일입니다: {}", dataPath); log.warn("이미 처리된 파일입니다: {}", dataPath);
@@ -83,7 +97,7 @@ public class GeoJsonDataService {
// 새 엔티티 생성 및 저장 // 새 엔티티 생성 및 저장
MapSheetLearnDataEntity entity = MapSheetLearnDataEntity entity =
createMapSheetLearnDataEntity(fileName, geoJsonContent, archiveFileName, geoJsonNode); createMapSheetLearnDataEntity(fileName, geoJsonContent, archiveFileName, geoJsonNode);
MapSheetLearnDataEntity savedEntity = mapSheetLearnDataRepository.save(entity); MapSheetLearnDataEntity savedEntity = mapSheetLearnDataRepository.save(entity);
return savedEntity.getId(); return savedEntity.getId();
@@ -108,7 +122,7 @@ public class GeoJsonDataService {
/** MapSheetLearnDataEntity 생성 */ /** MapSheetLearnDataEntity 생성 */
private MapSheetLearnDataEntity createMapSheetLearnDataEntity( private MapSheetLearnDataEntity createMapSheetLearnDataEntity(
String fileName, String geoJsonContent, String archiveFileName, JsonNode geoJsonNode) { String fileName, String geoJsonContent, String archiveFileName, JsonNode geoJsonNode) {
MapSheetLearnDataEntity entity = new MapSheetLearnDataEntity(); MapSheetLearnDataEntity entity = new MapSheetLearnDataEntity();
@@ -273,8 +287,8 @@ public class GeoJsonDataService {
JsonNode properties = firstFeature.get("properties"); JsonNode properties = firstFeature.get("properties");
// 학습 모델 특화 필드 확인 // 학습 모델 특화 필드 확인
return properties.has("cd_prob") return properties.has("cd_prob")
|| properties.has("class") || properties.has("class")
|| (properties.has("before") && properties.has("after")); || (properties.has("before") && properties.has("after"));
} }
} }
} }
@@ -285,9 +299,9 @@ public class GeoJsonDataService {
return false; return false;
} }
/** 학습 모델 결과의 geometry 데이터 처리 */ /** 학습 모델 결과의 geometry 데이터 처리 - 최적화된 배치 처리 */
@Transactional public void processLearningModelGeometryOptimized(
public void processLearningModelGeometry(Long dataUid, String geoJsonContent, String fileName) { Long dataUid, String geoJsonContent, String fileName) {
try { try {
log.info("학습 모델 geometry 데이터 처리 시작: {} (dataUid: {})", fileName, dataUid); log.info("학습 모델 geometry 데이터 처리 시작: {} (dataUid: {})", fileName, dataUid);
@@ -295,7 +309,7 @@ public class GeoJsonDataService {
// 메타데이터 추출 // 메타데이터 추출
String mapSheetName = String mapSheetName =
rootNode.has("name") ? rootNode.get("name").asText() : fileName.replace(".geojson", ""); rootNode.has("name") ? rootNode.get("name").asText() : fileName.replace(".geojson", "");
// 파일명에서 연도 및 지도번호 추출 (캠코_2021_2022_35813023) // 파일명에서 연도 및 지도번호 추출 (캠코_2021_2022_35813023)
String[] parts = mapSheetName.split("_"); String[] parts = mapSheetName.split("_");
@@ -318,36 +332,75 @@ public class GeoJsonDataService {
return; return;
} }
List<MapSheetLearnDataGeomEntity> geomEntities = new ArrayList<>(); // 소규모 배치로 나누어 처리
int totalFeatures = features.size();
int batchSize = 10; // 작은 배치 크기
int processedCount = 0; int processedCount = 0;
for (JsonNode feature : features) { log.info("총 {}개 feature를 {}개씩 배치로 나누어 처리", totalFeatures, batchSize);
try {
MapSheetLearnDataGeomEntity geomEntity = for (int i = 0; i < totalFeatures; i += batchSize) {
int endIndex = Math.min(i + batchSize, totalFeatures);
List<MapSheetLearnDataGeomEntity> batch = new ArrayList<>();
for (int j = i; j < endIndex; j++) {
try {
JsonNode feature = features.get(j);
MapSheetLearnDataGeomEntity geomEntity =
createGeometryEntity(feature, dataUid, beforeYear, afterYear, mapSheetNum); createGeometryEntity(feature, dataUid, beforeYear, afterYear, mapSheetNum);
if (geomEntity != null) { if (geomEntity != null) {
geomEntities.add(geomEntity); batch.add(geomEntity);
processedCount++; }
} catch (Exception e) {
log.warn("Feature geometry 처리 실패 (feature {}): {}", j + 1, e.getMessage());
} }
} catch (Exception e) {
log.warn("Feature geometry 처리 실패 (feature {}): {}", processedCount, e.getMessage());
} }
// 배치별 저장
if (!batch.isEmpty()) {
saveBatchGeometry(batch);
processedCount += batch.size();
log.debug("배치 {}-{} 처리 완료 ({}개)", i + 1, endIndex, batch.size());
}
// 메모리 정리
batch.clear();
} }
// 배치 저장 log.info("학습 모델 geometry 데이터 저장 완료: {} ({}개 feature)", fileName, processedCount);
if (!geomEntities.isEmpty()) {
mapSheetLearnDataGeomRepository.saveAll(geomEntities);
log.info("학습 모델 geometry 데이터 저장 완료: {} ({}개 feature)", fileName, geomEntities.size());
}
} catch (Exception e) { } catch (Exception e) {
log.error("학습 모델 geometry 데이터 처리 실패: {}", fileName, e); log.error("학습 모델 geometry 데이터 처리 실패: {}", fileName, e);
throw new RuntimeException("Geometry 처리 실패: " + fileName, e);
}
}
/** 배치별 geometry 저장 - 별도 트랜잭션 */
@Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
public void saveBatchGeometry(List<MapSheetLearnDataGeomEntity> batch) {
try {
if (batch == null || batch.isEmpty()) {
return;
}
mapSheetLearnDataGeomRepository.saveAll(batch);
} catch (Exception e) {
log.error("배치 geometry 저장 실패: {}", e.getMessage());
// 개별 저장 시도
for (MapSheetLearnDataGeomEntity entity : batch) {
try {
if (entity != null) {
mapSheetLearnDataGeomRepository.save(entity);
}
} catch (Exception individualError) {
log.warn("개별 geometry 저장 실패: {}", individualError.getMessage());
}
}
} }
} }
/** 개별 feature에서 geometry entity 생성 */ /** 개별 feature에서 geometry entity 생성 */
private MapSheetLearnDataGeomEntity createGeometryEntity( private MapSheetLearnDataGeomEntity createGeometryEntity(
JsonNode feature, Long dataUid, String beforeYear, String afterYear, String mapSheetNum) { JsonNode feature, Long dataUid, String beforeYear, String afterYear, String mapSheetNum) {
JsonNode properties = feature.get("properties"); JsonNode properties = feature.get("properties");
JsonNode geometry = feature.get("geometry"); JsonNode geometry = feature.get("geometry");

View File

@@ -2,7 +2,10 @@ package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -20,6 +23,11 @@ import org.hibernate.type.SqlTypes;
public class MapSheetLearnDataEntity { public class MapSheetLearnDataEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_sheet_learn_data_id_gen")
@SequenceGenerator(
name = "tb_map_sheet_learn_data_id_gen",
sequenceName = "tb_map_sheet_learn_data_data_uid",
allocationSize = 1)
@Column(name = "data_uid", nullable = false) @Column(name = "data_uid", nullable = false)
private Long id; private Long id;

View File

@@ -2,7 +2,10 @@ package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@@ -17,6 +20,13 @@ import org.locationtech.jts.geom.Geometry;
public class MapSheetLearnDataGeomEntity { public class MapSheetLearnDataGeomEntity {
@Id @Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "tb_map_sheet_learn_data_geom_id_gen")
@SequenceGenerator(
name = "tb_map_sheet_learn_data_geom_id_gen",
sequenceName = "tb_map_sheet_learn_data_geom_geom_uid",
allocationSize = 1)
@Column(name = "geo_uid", nullable = false) @Column(name = "geo_uid", nullable = false)
private Long id; private Long id;