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
67 changes: 67 additions & 0 deletions src/main/java/swyp/swyp6_team7/location/dao/CountryDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package swyp.swyp6_team7.location.dao;

import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import swyp.swyp6_team7.location.domain.Country;
import swyp.swyp6_team7.location.domain.Continent;

import java.util.Optional;

@Repository
public class CountryDao {
private final JdbcTemplate jdbcTemplate;

public CountryDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void addCountry(Country country) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

요거는 JPA 안쓰시고 JDBC 쓰신 이유가 있으실까요?

if (isCountryExists(country.getCountryName())) {
System.out.println("Duplicate country: " + country.getCountryName());
return;
}

String sql = "INSERT INTO countries (country_name, latitude, longitude, continent) VALUES (?, ?, ?, ?)";
try {
jdbcTemplate.update(sql,
country.getCountryName(),
country.getLatitude(),
country.getLongitude(),
country.getContinent().name());
} catch (DuplicateKeyException e) {
System.out.println("Already exists: " + country.getCountryName());
}
}

public boolean isCountryExists(String countryName) {
String sql = "SELECT COUNT(*) FROM countries WHERE country_name = ?";
Integer count = jdbcTemplate.queryForObject(sql, new Object[]{countryName}, Integer.class);
return count != null && count > 0;
}

public Optional<Country> findByCountryName(String countryName) {
String sql = "SELECT * FROM countries WHERE country_name = ?";
return jdbcTemplate.query(sql, new Object[]{countryName}, rs -> {
if (rs.next()) {
Country country = new Country();
country.setId(rs.getInt("country_id"));
country.setCountryName(rs.getString("country_name"));
country.setLatitude(rs.getDouble("latitude"));
country.setLongitude(rs.getDouble("longitude"));
country.setContinent(Continent.valueOf(rs.getString("continent")));
return Optional.of(country);
}
return Optional.empty();
});
}

public Country insertCountry(String countryName, Continent continent) {
String sql = "INSERT INTO countries (country_name, continent) VALUES (?, ?)";
jdbcTemplate.update(sql, countryName, continent.name());

return findByCountryName(countryName).orElseThrow(() ->
new IllegalStateException("Country insert failed: " + countryName));
}

}
56 changes: 38 additions & 18 deletions src/main/java/swyp/swyp6_team7/location/dao/LocationDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import swyp.swyp6_team7.location.domain.Country;
import swyp.swyp6_team7.location.domain.Location;
import swyp.swyp6_team7.location.domain.LocationType;

Expand All @@ -14,29 +15,48 @@ public LocationDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void addCity(Location location) {
if (location == null || location.getLocationName() == null) {
throw new IllegalArgumentException("Invalid location data: location or locationName is null");
public void addCity(Location location, Country country) {
String sql = "INSERT INTO locations (location_name, location_type, country_id) VALUES (?, ?, ?)";
try {
jdbcTemplate.update(sql,
location.getLocationName(),
location.getLocationType().name(),
country.getId());
} catch (DuplicateKeyException e) {
System.out.println("중복으로 삽입 불가 : " + location.getLocationName());
}
}

// 중복 데이터 체크
if (isLocationExists(location.getLocationName(), location.getLocationType())) {
System.out.println("Duplicate entry found for: " + location.getLocationName());
return; // 이미 존재하는 경우 삽입하지 않음
}
public boolean existsByLocationName(String locationName) {
String sql = "SELECT COUNT(*) FROM locations WHERE location_name = ?";
Integer count = jdbcTemplate.queryForObject(sql, new Object[]{locationName}, Integer.class);
return count != null && count > 0;
}

String sql = "INSERT INTO locations (location_name, location_type) VALUES (?, ?)";
try {
jdbcTemplate.update(sql, location.getLocationName(), location.getLocationType().name());
} catch (DuplicateKeyException e) {
System.out.println("Duplicate entry found for: " + location.getLocationName());
public void updateLocationWithCountry(String locationName, String countryName, Integer countryId) {
String locationType = countryName.equals("대한민국") ? "DOMESTIC" : "INTERNATIONAL";

String sql = "UPDATE locations " +
"SET country_id = ?, location_type = ? " +
"WHERE location_name = ?";

int rows = jdbcTemplate.update(sql, countryId, locationType, locationName);

if (rows > 0) {
//System.out.println(locationName + " 업데이트 완료 - country_id: " + countryId + ", type: " + locationType);
} else {
//System.out.println(locationName + " 은(는) 업데이트 대상 아님");
}
}

public boolean isLocationExists(String locationName, LocationType locationType) {
// locationName과 locationType을 기준으로 DB에 해당 레코드가 있는지 체크합니다.
String sql = "SELECT COUNT(*) FROM locations WHERE location_name = ? AND location_type = ?";
int count = jdbcTemplate.queryForObject(sql, new Object[]{locationName, locationType.name()}, Integer.class);
return count > 0;
public void updateCountryIdForMatchingLocationName(String countryName, Integer countryId) {
String sql = "UPDATE locations SET country_id = ?, location_type = ? WHERE location_name = ?";
String type = countryName.equals("대한민국") ? "DOMESTIC" : "INTERNATIONAL";

int rows = jdbcTemplate.update(sql, countryId, type, countryName);
if (rows > 0) {
//System.out.println("국가 location 업데이트됨 → " + countryName);
}
}

}
32 changes: 32 additions & 0 deletions src/main/java/swyp/swyp6_team7/location/domain/Continent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package swyp.swyp6_team7.location.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Arrays;

@Getter
@RequiredArgsConstructor
public enum Continent {
ASIA("아시아"),
EUROPE("유럽"),
NORTH_AMERICA("북아메리카"),
SOUTH_AMERICA("남아메리카"),
AFRICA("아프리카"),
OCEANIA("오세아니아"),
ANTARCTICA("남극");

private final String description;

public static Continent fromString(String value) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

요거 호출 빈도가 높으면 메모리 사용량이 꽤 될거같아서, String - Continent 쌍으로 Map 저장해도 좋을거같아요!

return Arrays.stream(values())
.filter(e -> e.description.equals(value))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("지원되지 않는 Continent입니다: " + value));
}

@Override
public String toString() {
return description;
}
}
32 changes: 32 additions & 0 deletions src/main/java/swyp/swyp6_team7/location/domain/Country.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package swyp.swyp6_team7.location.domain;

import jakarta.persistence.*;
import lombok.*;

@Getter
@Setter
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Table(name ="countries")
public class Country {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "country_id")
private Integer id;

@Column(name = "country_name",nullable = false, unique = true)
private String countryName;

private Double latitude;
private Double longitude;

@Enumerated(EnumType.STRING)
private Continent continent;

@Builder
public Country(String countryName, Continent continent) {
this.countryName = countryName;
this.continent = continent;
}
}
9 changes: 8 additions & 1 deletion src/main/java/swyp/swyp6_team7/location/domain/Location.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ public class Location {
@Column(name = "location_type")
private LocationType locationType;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "country_id", nullable = false)
private Country country;
Comment on lines +24 to +26
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

요건 개인적인 선호도인데... Join 을 명시적으로 안하고 코드 레벨에서 관리하면 어떨까 싶습니다. 회의할때 의견 나눠보시죵




@Builder
public Location(String locationName,LocationType locationType) {
public Location(String locationName,LocationType locationType, Country country) {
this.locationName = locationName;
this.locationType = locationType;
this.country = country;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package swyp.swyp6_team7.location.init;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import swyp.swyp6_team7.location.dao.CountryDao;
import swyp.swyp6_team7.location.domain.Country;
import swyp.swyp6_team7.location.parser.CountryParser;
import swyp.swyp6_team7.location.reader.CsvReader;

import java.io.InputStream;
import java.util.List;


@Component
@RequiredArgsConstructor
public class CountryInitializer {
private final CsvReader<Country> csvReader;
private final CountryParser countryParser;
private final CountryDao countryDao;

@PostConstruct
public void initCountries() {
try {
InputStream input = new ClassPathResource("cities/countries.csv").getInputStream();
List<Country> countries = csvReader.readByLine(input, countryParser, null);
countries.stream()
.filter(country -> country != null)
.forEach(countryDao::addCountry);
System.out.println("countries.csv 적재 완료 (" + countries.size() + "건)");
} catch (Exception e) {
System.err.println("countries.csv 적재 실패: " + e.getMessage());
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package swyp.swyp6_team7.location.init;

import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import swyp.swyp6_team7.location.service.LocationService;

@Component
@RequiredArgsConstructor
public class LocationInitializer {
private final LocationService locationService;

@PostConstruct
public void initLocations() {
try {
locationService.loadAllLocations();
} catch (Exception e) {
System.err.println("Location 초기 데이터 적재 실패: " + e.getMessage());
e.printStackTrace();
}
}
}
79 changes: 40 additions & 39 deletions src/main/java/swyp/swyp6_team7/location/parser/CityParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import swyp.swyp6_team7.location.dao.CountryDao;
import swyp.swyp6_team7.location.dao.LocationDao;
import swyp.swyp6_team7.location.domain.Continent;
import swyp.swyp6_team7.location.domain.Country;
import swyp.swyp6_team7.location.domain.Location;
import swyp.swyp6_team7.location.domain.LocationType;

Expand All @@ -18,55 +21,53 @@
public class CityParser implements Parser<Location> {

private final LocationDao locationDao;
private final CountryDao countryDao;

@Override
public Location parse(String line, LocationType locationType) {
String[] columns = line.split(",");

String location;

switch (locationType) {
case DOMESTIC:
location = columns[3].trim(); // 소분류 (시)
// '시' 제거 로직 추가
if (location.endsWith("시")) {
location = location.substring(0, location.length() - 1);
}
break;
// foreign: id,continent,country,city,city_en
// korea: id,country,region,city

case INTERNATIONAL:
location = columns[2].trim(); // 중분류 (나라)
String[] columns = line.split(",");
if (columns.length < 4) return null;

// 중복 체크 로직 추가
if (locationDao.isLocationExists(location, locationType)) {
// 이미 해당 나라가 DB에 있는 경우, 중복 추가 방지
System.out.println("Location already exists: " + location);
return null; // null 반환으로 추가하지 않음
}
break;
String countryName;
String cityName;
Continent continent = Continent.ASIA; // 기본값 국내 데이터 파싱에 사용

default:
throw new IllegalArgumentException("지원되지 않는 LocationType입니다: " + locationType);
if (locationType == LocationType.DOMESTIC) {
countryName = columns[1].trim(); // 대한민국
cityName = columns[3].trim().replace("시", ""); // 서울시 → 서울
} else {
countryName = columns[2].trim(); // 예: 미국
cityName = columns[3].trim(); // 예: 뉴욕
try {
continent = Continent.fromString(columns[1].trim());
} catch (Exception ignored) {
// 유럽/아시아 같은 애매한 값 무시
}
}

return new Location(location, locationType);
}
final Continent finalContinent = continent;

public void parseAndSave(String resourcePath, LocationType locationType) {
Resource resource = new ClassPathResource(resourcePath);
try (InputStream inputStream = resource.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
// country 조회 또는 신규 생성
Country country = countryDao.findByCountryName(countryName)
.orElseGet(() -> countryDao.insertCountry(countryName, finalContinent));

String line;
br.readLine(); // 첫 번째 행 건너뜀 (헤더)
while ((line = br.readLine()) != null) {
Location locationObject = parse(line, locationType);
if (locationObject != null) {
locationDao.addCity(locationObject);
}
}
} catch (IOException e) {
e.printStackTrace();
locationDao.updateCountryIdForMatchingLocationName(country.getCountryName(), country.getId());

// 이미 존재하면 업데이트, 없으면 삽입
if (locationDao.existsByLocationName(cityName)) {
locationDao.updateLocationWithCountry(cityName, country.getCountryName(), country.getId());
} else {
Location location = Location.builder()
.locationName(cityName)
.locationType(locationType)
.country(country)
.build();
locationDao.addCity(location, country);
}
return null;

}
}
Loading
Loading