diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java index f3c835da..56f027e4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java @@ -147,6 +147,6 @@ public class InnopamApiController { detectMastSearch.setCprsBfYr(cprsBfYr); detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); detectMastSearch.setFeatureId(featureId); - return detectMastService.findPnuDataDetail(detectMastSearch); + return new FeaturePnuDto(); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastPnuEntity.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastPnuEntity.java new file mode 100644 index 00000000..aef47a97 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastPnuEntity.java @@ -0,0 +1,48 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "detect_mast_pnu") +public class DetectMastPnuEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "detect_mast_pnu_id_gen") + @SequenceGenerator( + name = "detect_mast_pnu_id_gen", + sequenceName = "seq_detect_mast_pnu_id", + allocationSize = 1) + @Column(name = "dtct_mst_pnu_id", nullable = false) + private Long id; + + @NotNull + @ColumnDefault("gen_random_uuid()") + @Column(name = "detect_mast_pnu_uuid", nullable = false) + private UUID detectMastPnuUuid; + + @NotNull + @Column(name = "dtct_mst_id", nullable = false) + private Long dtctMstId; + + @Size(max = 4) + @NotNull + @Column(name = "pnu", nullable = false, length = 4) + private String pnu; + + @Column(name = "polygon", length = Integer.MAX_VALUE) + private String polygon; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepository.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepository.java new file mode 100644 index 00000000..b44f3677 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepository.java @@ -0,0 +1,7 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.repository; + +import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastPnuEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DetectMastPnuRepository + extends JpaRepository, DetectMastPnuRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepositoryCustom.java new file mode 100644 index 00000000..3029c658 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepositoryCustom.java @@ -0,0 +1,3 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.repository; + +public interface DetectMastPnuRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java index 1adf7774..21ea3d64 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -14,11 +14,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,12 +26,22 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class DetectMastService { - private final JsonFactory factory = new JsonFactory(); + @Value("${spring.profiles.active:local}") + private String profile; + private final DetectMastCoreService detectMastCoreService; + private final JsonFactory jsonFactory = new JsonFactory(); @Transactional public void saveDetectMast(DetectMastReq detectMast) { detectMastCoreService.saveDetectMast(detectMast); + // + // String dirPath = + // "local".equals(profile) + // ? "/Users/bokmin/detect/result/2023_2024/4" + // : detectMast.getPathNm(); + // + // List list = this.extractFeaturePnusRandom(dirPath); } public List selectDetectMast(DetectMastSearch detectMast) { @@ -43,29 +52,19 @@ public class DetectMastService { return detectMastCoreService.selectDetectMast(id); } + /** GeoJSON → polygon_id + 랜덤 PNU */ public List findPnuData(DetectMastSearch detectMast) { - String pathNm = detectMastCoreService.findPnuData(detectMast); - return this.extractFeaturePnusFast(pathNm); + + String dirPath = + "local".equals(profile) + ? "/Users/bokmin/detect/result/2023_2024/4" + : detectMastCoreService.findPnuData(detectMast); + + return extractFeaturePnusRandom(dirPath); } - public FeaturePnuDto findPnuDataDetail(DetectMastSearch detectMast) { - String pathNm = detectMastCoreService.findPnuData(detectMast); - List pnu = this.extractFeaturePnusFast(pathNm); - return pnu.get(0); - } - - private String randomPnu() { - ThreadLocalRandom r = ThreadLocalRandom.current(); - - String lawCode = String.valueOf(r.nextLong(1000000000L, 9999999999L)); // 10자리 - String sanFlag = r.nextBoolean() ? "1" : "2"; // 산/대지 - String bon = String.format("%04d", r.nextInt(1, 10000)); // 본번 - String bu = String.format("%04d", r.nextInt(0, 10000)); // 부번 - - return lawCode + sanFlag + bon + bu; - } - - public List extractFeaturePnusFast(String dirPath) { + /** 하위 폴더까지 .geojson 파일들에서 polygon_id만 뽑음 병렬처리(parallel) 제거: IO + parallel은 거의 항상 느려짐 */ + private List extractFeaturePnusRandom(String dirPath) { Path basePath = Paths.get(dirPath); if (!Files.isDirectory(basePath)) { @@ -73,20 +72,17 @@ public class DetectMastService { return List.of(); } - // 병렬로 모으기 위한 thread-safe 컬렉션 - Queue out = new ConcurrentLinkedQueue<>(); + List out = new ArrayList<>(4096); try (Stream stream = Files.walk(basePath)) { stream .filter(Files::isRegularFile) .filter(p -> p.toString().toLowerCase().endsWith(".geojson")) - .parallel() // 병렬 .forEach( p -> { try (InputStream in = Files.newInputStream(p); - JsonParser parser = factory.createParser(in)) { + JsonParser parser = jsonFactory.createParser(in)) { - // "polygon_id" 키를 만나면 다음 토큰 값을 읽어서 저장 while (parser.nextToken() != null) { if (parser.currentToken() == JsonToken.FIELD_NAME && "polygon_id".equals(parser.getCurrentName())) { @@ -94,14 +90,14 @@ public class DetectMastService { JsonToken next = parser.nextToken(); // 값으로 이동 if (next == JsonToken.VALUE_STRING) { String polygonId = parser.getValueAsString(); - out.add(new FeaturePnuDto(polygonId, this.randomPnu())); + out.add(new FeaturePnuDto(polygonId, randomPnu())); } } } } catch (Exception e) { - // 파일별 에러 로그는 최소화 - System.err.println("파싱 실패: " + p.getFileName() + " / " + e.getMessage()); + // 파일 단위 실패는 최소 로그 + System.err.println("GeoJSON 파싱 실패: " + p.getFileName() + " / " + e.getMessage()); } }); @@ -110,6 +106,18 @@ public class DetectMastService { return List.of(); } - return new ArrayList<>(out); + return out; + } + + /** 랜덤 PNU 생성 (임시) - 법정동코드(5) + 산구분(1) + 본번(4) + 부번(4) = 14자리 */ + private String randomPnu() { + ThreadLocalRandom r = ThreadLocalRandom.current(); + + String dongCode = String.format("%05d", r.nextInt(10000, 99999)); + String san = r.nextBoolean() ? "1" : "2"; + String bon = String.format("%04d", r.nextInt(1, 10000)); + String bu = String.format("%04d", r.nextInt(0, 10000)); + + return dongCode + san + bon + bu; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonGeometryConverter.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonGeometryConverter.java new file mode 100644 index 00000000..c1d8842e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonGeometryConverter.java @@ -0,0 +1,48 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; + +public class GeoJsonGeometryConverter { + + private static final GeometryFactory GF = new GeometryFactory(); + + public static Geometry toGeometry(JsonNode geomNode) { + String type = geomNode.path("type").asText(); + + if ("Polygon".equals(type)) { + return toPolygon(geomNode.path("coordinates")); + } + if ("MultiPolygon".equals(type)) { + return toMultiPolygon(geomNode.path("coordinates")); + } + return null; + } + + private static Polygon toPolygon(JsonNode coords) { + LinearRing shell = GF.createLinearRing(toCoords(coords.get(0))); + return GF.createPolygon(shell); + } + + private static MultiPolygon toMultiPolygon(JsonNode coords) { + Polygon[] polys = new Polygon[coords.size()]; + for (int i = 0; i < coords.size(); i++) { + polys[i] = toPolygon(coords.get(i)); + } + return GF.createMultiPolygon(polys); + } + + private static Coordinate[] toCoords(JsonNode ring) { + Coordinate[] c = new Coordinate[ring.size() + 1]; + for (int i = 0; i < ring.size(); i++) { + c[i] = new Coordinate(ring.get(i).get(0).asDouble(), ring.get(i).get(1).asDouble()); + } + c[c.length - 1] = c[0]; + return c; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonLoader.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonLoader.java new file mode 100644 index 00000000..879cdea4 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonLoader.java @@ -0,0 +1,44 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class GeoJsonLoader { + + private final ObjectMapper om = new ObjectMapper(); + + public GeoJsonFile load(File geoJsonFile) throws Exception { + + JsonNode root = om.readTree(geoJsonFile); + + long mapId = root.path("properties").path("map_id").asLong(-1); + if (mapId <= 0) { + throw new IllegalStateException( + "GeoJSON top-level properties.map_id 없음: " + geoJsonFile.getName()); + } + + List features = new ArrayList<>(); + root.path("features").forEach(features::add); + + return new GeoJsonFile(mapId, features); + } + + /** ✅ feature에서 polygon_id 추출 */ + public static String polygonId(JsonNode feature) { + return feature.path("properties").path("polygon_id").asText(null); + } + + public static class GeoJsonFile { + + public final long mapId; + public final List features; + + public GeoJsonFile(long mapId, List features) { + this.mapId = mapId; + this.features = features; + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/MapIdUtils.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/MapIdUtils.java new file mode 100644 index 00000000..2ab92670 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/MapIdUtils.java @@ -0,0 +1,17 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +public class MapIdUtils { + + private MapIdUtils() { + // util class + } + + /** map_id → 시도코드 예: 34602060 → "34" */ + public static String sidoCodeFromMapId(long mapId) { + String s = String.valueOf(mapId); + if (s.length() < 2) { + throw new IllegalArgumentException("잘못된 map_id: " + mapId); + } + return s.substring(0, 2); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpIndexManager.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpIndexManager.java new file mode 100644 index 00000000..60100995 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpIndexManager.java @@ -0,0 +1,76 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import java.io.File; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import org.geotools.api.data.DataStore; +import org.geotools.api.data.DataStoreFinder; +import org.geotools.api.data.SimpleFeatureSource; +import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.index.strtree.STRtree; + +public class ShpIndexManager { + + private static final String SHP_ROOT = "/shp"; + private static final String SHP_YYYYMM = "202512"; + private static final String PNU_FIELD = "PNU"; + + private final Map cache = new ConcurrentHashMap<>(); + + public STRtree getIndex(String sidoCode) { + return cache.computeIfAbsent(sidoCode, this::loadIndex); + } + + private STRtree loadIndex(String sidoCode) { + try { + String path = SHP_ROOT + "/LSMD_CONT_LDREG_" + sidoCode + "_" + SHP_YYYYMM + ".shp"; + + File shp = new File(path); + if (!shp.exists()) { + return null; + } + + STRtree index = new STRtree(10); + + DataStore store = DataStoreFinder.getDataStore(Map.of("url", shp.toURI().toURL())); + + String typeName = store.getTypeNames()[0]; + SimpleFeatureSource source = store.getFeatureSource(typeName); + SimpleFeatureCollection col = source.getFeatures(); + + try (SimpleFeatureIterator it = col.features()) { + while (it.hasNext()) { + SimpleFeature f = it.next(); + Geometry geom = (Geometry) f.getDefaultGeometry(); + String pnu = Objects.toString(f.getAttribute(PNU_FIELD), null); + if (geom != null && pnu != null) { + index.insert(geom.getEnvelopeInternal(), new ShpRow(geom, pnu)); + } + } + } + + index.build(); + store.dispose(); + return index; + + } catch (Exception e) { + return null; + } + } + + /** SHP 한 row */ + public static class ShpRow { + + public final Geometry geom; + public final String pnu; + + public ShpRow(Geometry geom, String pnu) { + this.geom = geom; + this.pnu = pnu; + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpPnuMatcher.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpPnuMatcher.java new file mode 100644 index 00000000..f61d3852 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpPnuMatcher.java @@ -0,0 +1,42 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import java.util.List; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; +import org.locationtech.jts.geom.prep.PreparedGeometryFactory; +import org.locationtech.jts.index.strtree.STRtree; + +public class ShpPnuMatcher { + + public static String pickByIntersectionMax(STRtree index, Geometry target) { + + Envelope env = target.getEnvelopeInternal(); + + @SuppressWarnings("unchecked") + List rows = index.query(env); + + double best = 0; + String bestPnu = null; + + for (ShpIndexManager.ShpRow row : rows) { + + PreparedGeometry prep = PreparedGeometryFactory.prepare(row.geom); + + if (prep.contains(target) || prep.covers(target)) { + return row.pnu; + } + + if (!prep.intersects(target)) { + continue; + } + + double area = row.geom.intersection(target).getArea(); + if (area > best) { + best = area; + bestPnu = row.pnu; + } + } + return bestPnu; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java index 5a739715..5014147e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -2,11 +2,9 @@ package com.kamco.cd.kamcoback.label; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,50 +15,46 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j @Tag(name = "라벨링 작업 관리", description = "라벨링 작업 관리") -@RequestMapping({"/api/label/mng"}) +@RequestMapping({"/api/label"}) @RequiredArgsConstructor @RestController public class LabelAllocateApiController { + private final LabelAllocateService labelAllocateService; + // 라벨링 수량 할당하는 로직 테스트 @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { // 도엽별 카운트 쿼리 - List sheets = - List.of( - new Sheet("1", 261), - new Sheet("2", 500), - new Sheet("3", 350), - new Sheet("4", 250), - new Sheet("5", 380), - new Sheet("6", 459)); + // List sheets = + // List.of( + // new Sheet("1", 261), + // new Sheet("2", 500), + // new Sheet("3", 350), + // new Sheet("4", 250), + // new Sheet("5", 380), + // new Sheet("6", 459)); // 사용자별 할당 입력한 값 + // List targets = + // List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700)); + // LabelAllocateService.allocateSheetCount(new ArrayList<>(sheets), new + // ArrayList<>(targets)); + + // targets.forEach( + // t -> { + // log.info("[" + t.getUserId() + "]"); + // t.getAssigned() + // .forEach( + // u -> { + // log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); + // }); + // }); + List targets = - List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700)); - - LabelAllocateService.allocate(new ArrayList<>(sheets), new ArrayList<>(targets)); - - targets.forEach( - t -> { - log.info("[" + t.getUserId() + "]"); - t.getAssigned() - .forEach( - u -> { - log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); - }); - }); - /** - * [A] 입력한 수 : 1000 - 도엽: 2 (500) - 도엽: 6 (459) - 도엽: 5 (41) - * - *

[B] 입력한 수 : 500 - 도엽: 5 (339) - 도엽: 3 (161) - * - *

[C] 입력한 수 : 700 - 도엽: 3 (189) - 도엽: 1 (261) - 도엽: 4 (250) - */ - // A 에게 도엽 2 asc 해서 500건 할당 -> 도엽 6 asc 해서 459 할당 -> 도엽 5 asc 해서 41건 할당 -> insert - // B 에게 도엽 5 위에 41건 할당한 것 빼고 asc 해서 339건 할당 -> 도엽 3 asc 해서 161건 할당 -> insert - // .... for문에서 할당한 것 빼고 asc 해서 건수만큼 할당 insert 하고 다음 으로 넘어가기 + List.of(new TargetUser("1", 1000), new TargetUser("2", 400), new TargetUser("3", 440)); + labelAllocateService.allocateAsc(targets); return ApiResponseDto.ok(null); } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java index 547284b6..59cdbd9a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java @@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.label.service; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; +import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; +import jakarta.transaction.Transactional; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -12,7 +14,19 @@ import org.springframework.stereotype.Service; @Service public class LabelAllocateService { - public static void allocate(List sheets, List targetUsers) { + private final LabelAllocateCoreService labelAllocateCoreService; + + public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { + this.labelAllocateCoreService = labelAllocateCoreService; + } + + /** + * 도엽 count 수와 할당된 count 수를 비교해서 많은 수부터 먼저 배정하고 나머지를 분배 배정하는 로직 + * + * @param sheets + * @param targetUsers + */ + public static void allocateSheetCount(List sheets, List targetUsers) { // 1️⃣ 실제 도엽 기준 할당 allocateSheets(sheets, targetUsers); @@ -75,4 +89,34 @@ public class LabelAllocateService { t.setShortage(share); } } + + /** + * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 + * + * @param targetUsers + */ + @Transactional + public void allocateAsc(List targetUsers) { + Long lastId = null; + + for (TargetUser target : targetUsers) { + int remaining = target.getDemand(); + + while (remaining > 0) { + + int batchSize = Math.min(remaining, 500); + List ids = labelAllocateCoreService.fetchNextIds(lastId, batchSize); + + if (ids.isEmpty()) { + return; // 더이상 할당할 데이터가 없으면 return + } + + labelAllocateCoreService.assignOwner( + ids, Long.valueOf(target.getUserId())); // TODO : userId를 숫자값을 가져올지 사번을 가져올지 고민 + + remaining -= ids.size(); + lastId = ids.get(ids.size() - 1); + } + } + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java new file mode 100644 index 00000000..d4f3da10 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java @@ -0,0 +1,21 @@ +package com.kamco.cd.kamcoback.postgres.core; + +import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LabelAllocateCoreService { + + private final LabelAllocateRepository labelAllocateRepository; + + public List fetchNextIds(Long lastId, int batchSize) { + return labelAllocateRepository.fetchNextIds(lastId, batchSize); + } + + public Long assignOwner(List ids, Long userId) { + return labelAllocateRepository.assignOwner(ids, userId); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepository.java new file mode 100644 index 00000000..976a083e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepository.java @@ -0,0 +1,8 @@ +package com.kamco.cd.kamcoback.postgres.repository.label; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LabelAllocateRepository + extends JpaRepository, + LabelAllocateRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java new file mode 100644 index 00000000..fa4ae5e5 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.kamco.cd.kamcoback.postgres.repository.label; + +import java.util.List; + +public interface LabelAllocateRepositoryCustom { + + List fetchNextIds(Long lastId, int batchSize); + + Long assignOwner(List ids, Long userId); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java new file mode 100644 index 00000000..dfdec9ae --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java @@ -0,0 +1,51 @@ +package com.kamco.cd.kamcoback.postgres.repository.label; + +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport + implements LabelAllocateRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + public LabelAllocateRepositoryImpl(JPAQueryFactory queryFactory) { + super(MapSheetAnalDataGeomEntity.class); + this.queryFactory = queryFactory; + } + + @Override + public List fetchNextIds(Long lastId, int batchSize) { + return queryFactory + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(2022), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(2024), + mapSheetAnalDataInferenceGeomEntity.labelerUid.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.asc()) + .limit(batchSize) + .fetch(); + } + + @Override + public Long assignOwner(List ids, Long userId) { + return queryFactory + .update(mapSheetAnalDataInferenceGeomEntity) + .set(mapSheetAnalDataInferenceGeomEntity.labelerUid, userId) + .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) + .execute(); + } +}