Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<String> SEOUL_NAMES = List.of(SEOUL, "서울");
private static final List<String> SEOUL_DISTRICTS = List.of(
"종로구", "중구", "용산구", "성동구", "광진구",
"동대문구", "중랑구", "성북구", "강북구", "도봉구",
"노원구", "은평구", "서대문구", "마포구", "양천구",
"강서구", "구로구", "금천구", "영등포구", "동작구",
"관악구", "서초구", "강남구", "송파구", "강동구"
);

private final RegionRepository regionRepository;

@Override
@Transactional
public void run(ApplicationArguments args) {
List<Region> 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<Region> seoulRegions = existingSeoulRegions.isEmpty()
? List.of(seoul)
: existingSeoulRegions;

Set<String> existingDistrictNames = regionRepository
.findAllByParentInAndRegionLevel(seoulRegions, RegionLevel.SIGUNGU)
.stream()
.map(Region::getRegionName)
.collect(Collectors.toSet());

List<Region> missingDistricts = SEOUL_DISTRICTS.stream()
.filter(name -> !existingDistrictNames.contains(name))
.map(name -> new Region(seoul, name, RegionLevel.SIGUNGU))
.toList();

if (!missingDistricts.isEmpty()) {
regionRepository.saveAll(missingDistricts);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,6 +16,16 @@ public interface RegionRepository extends JpaRepository<Region, Long> {
@EntityGraph(attributePaths = "parent")
List<Region> findAllByRegionIdIn(Collection<Long> regionIds);

List<Region> findAllByParentIsNullAndRegionLevelAndRegionNameIn(
RegionLevel regionLevel,
Collection<String> regionNames
);

List<Region> findAllByParentInAndRegionLevel(
Collection<Region> parents,
RegionLevel regionLevel
);

// 관광 지역코드 -> Region 역매핑 (북마크 시 Place.region 세팅용). 미매핑 시 empty.
Optional<Region> findFirstByAreaCodeAndSigunguCode(Integer areaCode, Integer sigunguCode);
}
Original file line number Diff line number Diff line change
@@ -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<String> SEOUL_DISTRICTS = List.of(
"종로구", "중구", "용산구", "성동구", "광진구",
"동대문구", "중랑구", "성북구", "강북구", "도봉구",
"노원구", "은평구", "서대문구", "마포구", "양천구",
"강서구", "구로구", "금천구", "영등포구", "동작구",
"관악구", "서초구", "강남구", "송파구", "강동구"
);

@Autowired
RegionRepository regionRepository;

@Test
void seedsSeoulAndTwentyFiveDistricts() throws Exception {
RegionDataInitializer initializer = new RegionDataInitializer(regionRepository);

initializer.run(null);

List<Region> regions = regionRepository.findAllByOrderByRegionIdAsc();
List<Region> 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);
Comment on lines +64 to +68

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add coverage for the legacy 서울 root.

The tests do not verify the stated behavior for an existing SIDO named "서울". Add a case that seeds this root, runs the initializer, and verifies that it remains the only root and has 25 districts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/daytodo/domain/region/config/RegionDataInitializerTest.java`
around lines 64 - 68, Add a test case alongside
insertsOnlyMissingDistrictsWhenSomeDataAlreadyExists that seeds an existing SIDO
named "서울", runs RegionDataInitializer.run, and verifies the legacy root remains
the sole root with exactly 25 districts. Use the existing repository and
assertion patterns without changing initializer behavior.


List<Region> 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);
}
}
Loading