diff --git a/src/main/java/com/daytodo/domain/region/config/RegionDataInitializer.java b/src/main/java/com/daytodo/domain/region/config/RegionDataInitializer.java new file mode 100644 index 0000000..37d765a --- /dev/null +++ b/src/main/java/com/daytodo/domain/region/config/RegionDataInitializer.java @@ -0,0 +1,64 @@ +package com.daytodo.domain.region.config; + +import com.daytodo.domain.region.entity.Region; +import com.daytodo.domain.region.enums.RegionLevel; +import com.daytodo.domain.region.repository.RegionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class RegionDataInitializer implements ApplicationRunner { + private static final String SEOUL = "서울특별시"; + private static final List SEOUL_NAMES = List.of(SEOUL, "서울"); + private static final List SEOUL_DISTRICTS = List.of( + "종로구", "중구", "용산구", "성동구", "광진구", + "동대문구", "중랑구", "성북구", "강북구", "도봉구", + "노원구", "은평구", "서대문구", "마포구", "양천구", + "강서구", "구로구", "금천구", "영등포구", "동작구", + "관악구", "서초구", "강남구", "송파구", "강동구" + ); + + private final RegionRepository regionRepository; + + @Override + @Transactional + public void run(ApplicationArguments args) { + List existingSeoulRegions = regionRepository + .findAllByParentIsNullAndRegionLevelAndRegionNameIn(RegionLevel.SIDO, SEOUL_NAMES); + + Region seoul = existingSeoulRegions.stream() + .filter(region -> SEOUL.equals(region.getRegionName())) + .findFirst() + .orElseGet(() -> existingSeoulRegions.stream().findFirst() + .orElseGet(() -> regionRepository.save( + new Region(null, SEOUL, RegionLevel.SIDO) + ))); + + List seoulRegions = existingSeoulRegions.isEmpty() + ? List.of(seoul) + : existingSeoulRegions; + + Set existingDistrictNames = regionRepository + .findAllByParentInAndRegionLevel(seoulRegions, RegionLevel.SIGUNGU) + .stream() + .map(Region::getRegionName) + .collect(Collectors.toSet()); + + List missingDistricts = SEOUL_DISTRICTS.stream() + .filter(name -> !existingDistrictNames.contains(name)) + .map(name -> new Region(seoul, name, RegionLevel.SIGUNGU)) + .toList(); + + if (!missingDistricts.isEmpty()) { + regionRepository.saveAll(missingDistricts); + } + } +} diff --git a/src/main/java/com/daytodo/domain/region/repository/RegionRepository.java b/src/main/java/com/daytodo/domain/region/repository/RegionRepository.java index f698f04..a9b47b6 100644 --- a/src/main/java/com/daytodo/domain/region/repository/RegionRepository.java +++ b/src/main/java/com/daytodo/domain/region/repository/RegionRepository.java @@ -1,6 +1,7 @@ package com.daytodo.domain.region.repository; import com.daytodo.domain.region.entity.Region; +import com.daytodo.domain.region.enums.RegionLevel; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; @@ -15,6 +16,16 @@ public interface RegionRepository extends JpaRepository { @EntityGraph(attributePaths = "parent") List findAllByRegionIdIn(Collection regionIds); + List findAllByParentIsNullAndRegionLevelAndRegionNameIn( + RegionLevel regionLevel, + Collection regionNames + ); + + List findAllByParentInAndRegionLevel( + Collection parents, + RegionLevel regionLevel + ); + // 관광 지역코드 -> Region 역매핑 (북마크 시 Place.region 세팅용). 미매핑 시 empty. Optional findFirstByAreaCodeAndSigunguCode(Integer areaCode, Integer sigunguCode); } diff --git a/src/test/java/com/daytodo/domain/region/config/RegionDataInitializerTest.java b/src/test/java/com/daytodo/domain/region/config/RegionDataInitializerTest.java new file mode 100644 index 0000000..e077a9c --- /dev/null +++ b/src/test/java/com/daytodo/domain/region/config/RegionDataInitializerTest.java @@ -0,0 +1,80 @@ +package com.daytodo.domain.region.config; + +import com.daytodo.domain.region.entity.Region; +import com.daytodo.domain.region.enums.RegionLevel; +import com.daytodo.domain.region.repository.RegionRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@ActiveProfiles("test") +class RegionDataInitializerTest { + private static final List SEOUL_DISTRICTS = List.of( + "종로구", "중구", "용산구", "성동구", "광진구", + "동대문구", "중랑구", "성북구", "강북구", "도봉구", + "노원구", "은평구", "서대문구", "마포구", "양천구", + "강서구", "구로구", "금천구", "영등포구", "동작구", + "관악구", "서초구", "강남구", "송파구", "강동구" + ); + + @Autowired + RegionRepository regionRepository; + + @Test + void seedsSeoulAndTwentyFiveDistricts() throws Exception { + RegionDataInitializer initializer = new RegionDataInitializer(regionRepository); + + initializer.run(null); + + List regions = regionRepository.findAllByOrderByRegionIdAsc(); + List districts = regions.stream() + .filter(region -> region.getRegionLevel() == RegionLevel.SIGUNGU) + .toList(); + + assertThat(regions).hasSize(26); + assertThat(districts) + .extracting(Region::getRegionName) + .containsExactlyElementsOf(SEOUL_DISTRICTS); + assertThat(districts) + .allSatisfy(district -> assertThat(district.getParent().getRegionName()) + .isEqualTo("서울특별시")); + } + + @Test + void doesNotInsertDuplicatesWhenRunAgain() throws Exception { + RegionDataInitializer initializer = new RegionDataInitializer(regionRepository); + + initializer.run(null); + initializer.run(null); + + assertThat(regionRepository.count()).isEqualTo(26); + assertThat(regionRepository.findAllByOrderByRegionIdAsc().stream() + .filter(region -> region.getRegionLevel() == RegionLevel.SIGUNGU) + .map(Region::getRegionName)) + .containsExactlyElementsOf(SEOUL_DISTRICTS); + } + + @Test + void insertsOnlyMissingDistrictsWhenSomeDataAlreadyExists() throws Exception { + Region seoul = regionRepository.save(new Region(null, "서울특별시", RegionLevel.SIDO)); + regionRepository.save(new Region(seoul, "종로구", RegionLevel.SIGUNGU)); + + new RegionDataInitializer(regionRepository).run(null); + + List regions = regionRepository.findAllByOrderByRegionIdAsc(); + assertThat(regions).hasSize(26); + assertThat(regions.stream() + .filter(region -> "종로구".equals(region.getRegionName()))) + .hasSize(1); + assertThat(regions.stream() + .filter(region -> region.getRegionLevel() == RegionLevel.SIGUNGU) + .map(Region::getRegionName)) + .containsExactlyInAnyOrderElementsOf(SEOUL_DISTRICTS); + } +}