From 3fb6bf13a10d321955161a427a6c6929ab919237 Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Mon, 2 Feb 2026 14:10:04 +0900 Subject: [PATCH 01/12] feat: Add Flyway database migration support * Added Flyway core and MySQL dependencies in build.gradle * Enabled Flyway in application configurations for both production and default profiles * Configured Flyway settings including baseline and validation options This update facilitates database version control and migration management. --- build.gradle | 2 ++ src/main/resources/application-prod.yml | 5 +++++ src/main/resources/application.yml | 8 +++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b323788..73cc788 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,8 @@ dependencies { implementation 'software.amazon.awssdk:s3:2.20.89' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.flywaydb:flyway-core' + implementation 'org.flywaydb:flyway-mysql' compileOnly 'org.projectlombok:lombok' diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 2a261b0..3216900 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -6,6 +6,11 @@ spring: activate: on-profile: prod + flyway: + enabled: true + baseline-on-migrate: true + validate-on-migrate: true + memory: base-url: https://voyame-studio.org diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b9cfe59..8ab62d3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -23,7 +23,7 @@ spring: jpa: hibernate: - ddl-auto: update + ddl-auto: validate show-sql: true properties: hibernate: @@ -39,6 +39,12 @@ spring: globally_quoted_identifiers: true globally_quoted_identifiers_skip_column_definitions: true + flyway: + enabled: true + baseline-on-migrate: false + validate-on-migrate: true + locations: classpath:db/migration + logging: level: org.hibernate.SQL: debug From c4707da455397a5b6bbd0ad425acdd7e424dcc8a Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Mon, 2 Feb 2026 14:18:50 +0900 Subject: [PATCH 02/12] refactor: Remove Country and Data Initializers and add SQL * Deleted CountryInitializer and DataInitializer classes as they are no longer needed. * Added initial SQL migration scripts for database schema and basic data setup, including country, emotion, weather, trip theme, and user data. This cleanup streamlines the initialization process and leverages database migrations for data management. --- .../tave/memory/init/CountryInitializer.java | 18 -- .../zim/tave/memory/init/DataInitializer.java | 220 --------------- .../tave/memory/service/CountryService.java | 265 +----------------- src/main/resources/db/migration/V1__init.sql | 169 +++++++++++ .../db/migration/V2__insert_basic_data.sql | 48 ++++ .../db/migration/V3__insert_countries.sql | 251 +++++++++++++++++ .../resources/db/migration/V4__test_user.sql | 45 +++ 7 files changed, 514 insertions(+), 502 deletions(-) delete mode 100644 src/main/java/zim/tave/memory/init/CountryInitializer.java delete mode 100644 src/main/java/zim/tave/memory/init/DataInitializer.java create mode 100644 src/main/resources/db/migration/V1__init.sql create mode 100644 src/main/resources/db/migration/V2__insert_basic_data.sql create mode 100644 src/main/resources/db/migration/V3__insert_countries.sql create mode 100644 src/main/resources/db/migration/V4__test_user.sql diff --git a/src/main/java/zim/tave/memory/init/CountryInitializer.java b/src/main/java/zim/tave/memory/init/CountryInitializer.java deleted file mode 100644 index f4afbbf..0000000 --- a/src/main/java/zim/tave/memory/init/CountryInitializer.java +++ /dev/null @@ -1,18 +0,0 @@ -package zim.tave.memory.init; - -import lombok.RequiredArgsConstructor; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; -import zim.tave.memory.service.CountryService; - -@Component -@RequiredArgsConstructor -public class CountryInitializer implements CommandLineRunner { - - private final CountryService countryService; - - @Override - public void run(String... args) { - countryService.init(); - } -} diff --git a/src/main/java/zim/tave/memory/init/DataInitializer.java b/src/main/java/zim/tave/memory/init/DataInitializer.java deleted file mode 100644 index 1c114e1..0000000 --- a/src/main/java/zim/tave/memory/init/DataInitializer.java +++ /dev/null @@ -1,220 +0,0 @@ -package zim.tave.memory.init; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import zim.tave.memory.domain.*; -import zim.tave.memory.repository.*; - -@Slf4j -@Component -@RequiredArgsConstructor -public class DataInitializer implements CommandLineRunner { - - private final TripThemeRepository tripThemeRepository; - private final EmotionRepository emotionRepository; - private final WeatherRepository weatherRepository; - private final UserRepository userRepository; - private final BoardThemeRepository boardThemeRepository; - private final StickerRepository stickerRepository; - - @Override - @Transactional - public void run(String... args) throws Exception { - log.info("기본 데이터 초기화 시작..."); - - // 테마 데이터 초기화 - initTripThemes(); - - // 감정 데이터 초기화 - initEmotions(); - - // 날씨 데이터 초기화 - initWeathers(); - - // 보드 데이터 초기화 - initBoardThemes(); - - // 보드 스티커 초기화 - initStickers(); - - // 테스트 사용자 데이터 초기화 - initTestUser(); - - log.info("기본 데이터 초기화 완료!"); - } - - private void initTripThemes() { - if (tripThemeRepository.count() == 0) { - log.info("여행 테마 데이터 생성 중..."); - - // 기본 테마 (ID 1) - TripTheme basicTheme = new TripTheme("기본", - "https://me-mory01.mooo.com/api/files?key=images/d69be93f-fef6-4a73-815f-ea6b93fd2d59_trip_thumb_default.png", - "https://me-mory01.mooo.com/api/files?key=images/6e56681d-9fc3-43d2-a803-0ef165c63c3c_trip_card_default.png"); - tripThemeRepository.save(basicTheme); - - // Grey 테마 (ID 2) - TripTheme greyTheme = new TripTheme("Grey", - "https://me-mory01.mooo.com/api/files?key=images/f0d5e1f7-2718-4dac-a780-d6f6e5f2d42a_trip_thumb_grey.png", - "https://me-mory01.mooo.com/api/files?key=images/462d91ad-b618-4449-805f-35f07187247b_trip_card_grey.png"); - tripThemeRepository.save(greyTheme); - - // 탑승권 테마 (ID 3) - TripTheme ticketTheme = new TripTheme("탑승권", - "https://me-mory01.mooo.com/api/files?key=images/cbcf48b8-8469-4e0f-b597-7ef27cc6190e_trip_thumb_boardingpass.png", - "https://me-mory01.mooo.com/api/files?key=images/3e637461-f9a2-477d-ae06-4245033174a9_trip_card_boardingpass.png"); - tripThemeRepository.save(ticketTheme); - - // 액자 테마 (ID 4) - TripTheme frameTheme = new TripTheme("액자", - "https://me-mory01.mooo.com/api/files?key=images/c3efb8f8-f4a8-41a1-9d29-e93bc9d2dd83_trip_thumb_frame.png", - "https://me-mory01.mooo.com/api/files?key=images/eade6dc1-c412-4924-af7f-832292b258c0_trip_card_frame.png"); - tripThemeRepository.save(frameTheme); - - // Beach 테마 (ID 5) - TripTheme beachTheme = new TripTheme("Beach", - "https://me-mory01.mooo.com/api/files?key=images/f692134b-0676-47e9-9782-778f7df9a23a_trip_thumb_beach.png", - "https://me-mory01.mooo.com/api/files?key=images/94abb8d5-5f56-4061-8e90-1df9c9107caf_trip_card_beach.png"); - tripThemeRepository.save(beachTheme); - - // Forest 테마 (ID 6) - TripTheme forestTheme = new TripTheme("Forest", - "https://me-mory01.mooo.com/api/files?key=images/6f40c3c9-313f-44e5-9624-3264991b2f9b_trip_thumb_forest.png", - "https://me-mory01.mooo.com/api/files?key=images/99589e53-9539-46eb-b702-644fab7a87e3_trip_card_forest.png"); - tripThemeRepository.save(forestTheme); - - log.info("여행 테마 데이터 생성 완료: {}개", tripThemeRepository.count()); - } - } - - private void initEmotions() { - if (emotionRepository.count() == 0) { - log.info("감정 데이터 생성 중..."); - - emotionRepository.save(new Emotion("기본", "#EEEEEE")); - emotionRepository.save(new Emotion("설렘", "#FDD7DE")); - emotionRepository.save(new Emotion("신기함", "#FFCB6B")); - emotionRepository.save(new Emotion("즐거움", "#FFE13E")); - emotionRepository.save(new Emotion("힐링", "#C1E8A0")); - emotionRepository.save(new Emotion("평온", "#D1E5D4")); - emotionRepository.save(new Emotion("뿌듯함", "#5ACFD5")); - emotionRepository.save(new Emotion("해방감", "#5EB6D9")); - emotionRepository.save(new Emotion("낯섦", "#634E72")); - emotionRepository.save(new Emotion("긴장됨", "#2C3E50")); - emotionRepository.save(new Emotion("외로움", "#A9A9B0")); - emotionRepository.save(new Emotion("아쉬움", "#866868")); - emotionRepository.save(new Emotion("벅참", "#800020")); - - - log.info("감정 데이터 생성 완료: {}개", emotionRepository.count()); - } - } - - private void initWeathers() { - if (weatherRepository.count() == 0) { - log.info("날씨 데이터 생성 중..."); - - Weather sunny = new Weather(); - sunny.setName("맑음"); - sunny.setIconUrl("https://me-mory01.mooo.com/api/files?key=images/7de47dfb-6f09-47d2-9268-2b8c49c2f9bd_weather_sunny.png"); - weatherRepository.save(sunny); - - Weather cloudy = new Weather(); - cloudy.setName("구름"); - cloudy.setIconUrl("https://me-mory01.mooo.com/api/files?key=images/6a8c4a38-0ccd-479c-ba70-7202d7535bf7_weather_cloudy.png"); - weatherRepository.save(cloudy); - - Weather rainy = new Weather(); - rainy.setName("비"); - rainy.setIconUrl("https://me-mory01.mooo.com/api/files?key=images/72a8e191-9b31-4646-ae48-cb31fb543de7_weather_rainy.png"); - weatherRepository.save(rainy); - - Weather windy = new Weather(); - windy.setName("바람"); - windy.setIconUrl("https://me-mory01.mooo.com/api/files?key=images/dae2b4e2-7ac4-45fe-bccc-be835218d8d4_weather_windy.png"); - weatherRepository.save(windy); - - Weather snowy = new Weather(); - snowy.setName("눈"); - snowy.setIconUrl("https://me-mory01.mooo.com/api/files?key=images/3b298957-360e-4ea4-86ea-bcd65fcd3e77_weather_snowy.png"); - weatherRepository.save(snowy); - - log.info("날씨 데이터 생성 완료: {}개", weatherRepository.count()); - } - } - - private void initBoardThemes() { - if (boardThemeRepository.count() == 0) { - log.info("보드 테마 데이터 생성 중..."); - - BoardTheme chalkboard = new BoardTheme(); - chalkboard.setThemeName("칠판"); - chalkboard.setThumbnailUrl("https://me-mory01.mooo.com/api/files?key=images/board_theme/chalkboard_thumb.png"); - chalkboard.setCardUrl("https://me-mory01.mooo.com/api/files?key=images/73212582-a940-4929-9c9b-f99ce68f3238_board_card_chalkboard.png"); - boardThemeRepository.save(chalkboard); - - BoardTheme tablecloth = new BoardTheme(); - tablecloth.setThemeName("식탁보"); - tablecloth.setThumbnailUrl("https://me-mory01.mooo.com/api/files?key=images/board_theme/tablecloth_thumb.png"); - tablecloth.setCardUrl("https://me-mory01.mooo.com/api/files?key=images/ea8230ad-a70a-4f87-acb4-648add56b1b8_board_card_tablecloth.png"); - boardThemeRepository.save(tablecloth); - - BoardTheme board = new BoardTheme(); - board.setThemeName("나무보드"); - board.setThumbnailUrl("https://me-mory01.mooo.com/api/files?key=images/board_theme/board_thumb.png"); - board.setCardUrl("https://me-mory01.mooo.com/api/files?key=images/ecee4553-72cc-4097-8d20-10a7ee889269_board_card_woodboard.png"); - boardThemeRepository.save(board); - - BoardTheme chess = new BoardTheme(); - chess.setThemeName("체스판"); - chess.setThumbnailUrl("https://me-mory01.mooo.com/api/files?key=images/board_theme/chess_thumb.png"); - chess.setCardUrl("https://me-mory01.mooo.com/api/files?key=images/e026afe7-1619-4ee5-b3e7-c2564d88447c_board_card_chess.png"); - boardThemeRepository.save(chess); - - log.info("보드 테마 데이터 생성 완료: {}개", boardThemeRepository.count()); - } - } - - private void initStickers() { - if (stickerRepository.count() == 0) { - log.info("스티커 데이터 생성 중..."); - - Sticker starSticker = new Sticker(); - starSticker.setName("Star"); - starSticker.setImageUrl("https://me-mory01.mooo.com/api/files?key=images/6d4c8b2f-00ec-41ae-bd57-9ce914e1c78d_sticker_star.png"); - - stickerRepository.save(starSticker); - - log.info("스티커 데이터 생성 완료: {}개", stickerRepository.count()); - } - } - - private void initTestUser() { - // 테스트용 사용자가 없으면 생성 - if (!userRepository.findByKakaoId("test_강지혜").isPresent()) { - log.info("테스트 사용자 데이터 생성 중..."); - - User testUser = new User(); - testUser.setKakaoId("test_강지혜"); - testUser.setSurName("KANG"); - testUser.setFirstName("JIHYE"); - testUser.setKoreanName("강지혜"); - testUser.setBirth(java.time.LocalDate.of(1995, 3, 15)); - testUser.setNationality("REPUBLIC OF KOREA"); - testUser.setCreatedAt(java.time.LocalDate.now()); - testUser.setStatus(true); - testUser.setProfileImageUrl("https://me-mory.mooo.com/api/files?key=images/7c24aa61-2d36-48fd-80a5-646ac518256d_IMG_3831.jpg"); - testUser.setDiaryCount(0L); - testUser.setVisitedCountryCount(0L); - testUser.setFlags("🇰🇷"); - userRepository.save(testUser); - - log.info("테스트 사용자 데이터 생성 완료"); - } else { - log.info("테스트 사용자가 이미 존재합니다."); - } - } -} diff --git a/src/main/java/zim/tave/memory/service/CountryService.java b/src/main/java/zim/tave/memory/service/CountryService.java index fb01ae2..1b30183 100644 --- a/src/main/java/zim/tave/memory/service/CountryService.java +++ b/src/main/java/zim/tave/memory/service/CountryService.java @@ -53,267 +53,4 @@ public void saveCountry(Country country) { } countryRepository.save(country); } - - @Transactional - public void init() { - log.info("CountryService.init() 실행됨"); - if (!countryRepository.findAll().isEmpty()) { - log.info("이미 Country 데이터가 존재함 → init 종료"); - return; - } - - - List countries = List.of( - new Country("GH", "가나", "🇬🇭"), - new Country("GA", "가봉", "🇬🇦"), - new Country("GY", "가이아나", "🇬🇾"), - new Country("GM", "감비아", "🇬🇲"), - new Country("GG", "건지", "🇬🇬"), - new Country("GP", "과들루프", "🇬🇵"), - new Country("GT", "과테말라", "🇬🇹"), - new Country("GU", "괌", "🇬🇺"), - new Country("GD", "그레나다", "🇬🇩"), - new Country("GR", "그리스", "🇬🇷"), - new Country("GL", "그린란드", "🇬🇱"), - new Country("GN", "기니", "🇬🇳"), - new Country("GW", "기니비사우", "🇬🇼"), - new Country("NA", "나미비아", "🇳🇦"), - new Country("NR", "나우루", "🇳🇷"), - new Country("NG", "나이지리아", "🇳🇬"), - new Country("AQ", "남극", "🇦🇶"), - new Country("SS", "남수단", "🇸🇸"), - new Country("ZA", "남아프리카공화국", "🇿🇦"), - new Country("NL", "네덜란드", "🇳🇱"), - new Country("NP", "네팔", "🇳🇵"), - new Country("NO", "노르웨이", "🇳🇴"), - new Country("NF", "노퍽섬", "🇳🇫"), - new Country("NZ", "뉴질랜드", "🇳🇿"), - new Country("NC", "뉴칼레도니아", "🇳🇨"), - new Country("NU", "니우에", "🇳🇺"), - new Country("NE", "니제르", "🇳🇪"), - new Country("NI", "니카라과", "🇳🇮"), - new Country("TW", "대만", "🇹🇼"), - new Country("KR", "대한민국", "🇰🇷"), - new Country("DK", "덴마크", "🇩🇰"), - new Country("DM", "도미니카", "🇩🇲"), - new Country("DO", "도미니카 공화국", "🇩🇴"), - new Country("DE", "독일", "🇩🇪"), - new Country("TL", "동티모르", "🇹🇱"), - new Country("LA", "라오스", "🇱🇦"), - new Country("LR", "라이베리아", "🇱🇷"), - new Country("LV", "라트비아", "🇱🇻"), - new Country("RU", "러시아", "🇷🇺"), - new Country("LB", "레바논", "🇱🇧"), - new Country("LS", "레소토", "🇱🇸"), - new Country("RE", "레위니옹", "🇷🇪"), - new Country("RO", "루마니아", "🇷🇴"), - new Country("LU", "룩셈부르크", "🇱🇺"), - new Country("RW", "르완다", "🇷🇼"), - new Country("LY", "리비아", "🇱🇾"), - new Country("LT", "리투아니아", "🇱🇹"), - new Country("LI", "리히텐슈타인", "🇱🇮"), - new Country("MG", "마다가스카르", "🇲🇬"), - new Country("MQ", "마르티니크", "🇲🇶"), - new Country("MH", "마셜제도", "🇲🇭"), - new Country("YT", "마요트", "🇾🇹"), - new Country("MO", "마카오", "🇲🇴"), - new Country("MW", "말라위", "🇲🇼"), - new Country("MY", "말레이시아", "🇲🇾"), - new Country("ML", "말리", "🇲🇱"), - new Country("IM", "맨섬", "🇮🇲"), - new Country("MX", "멕시코", "🇲🇽"), - new Country("MC", "모나코", "🇲🇨"), - new Country("MA", "모로코", "🇲🇦"), - new Country("MR", "모리타니", "🇲🇷"), - new Country("MZ", "모잠비크", "🇲🇿"), - new Country("ME", "몬테네그로", "🇲🇪"), - new Country("MS", "몬트세라트", "🇲🇸"), - new Country("MD", "몰도바", "🇲🇩"), - new Country("MV", "몰디브", "🇲🇻"), - new Country("MT", "몰타", "🇲🇹"), - new Country("MN", "몽골", "🇲🇳"), - new Country("US", "미국", "🇺🇸"), - new Country("UM", "미국령 군소 제도", "🇺🇲"), - new Country("VI", "미국령 버진아일랜드", "🇻🇮"), - new Country("MM", "미얀마", "🇲🇲"), - new Country("FM", "미크로네시아", "🇫🇲"), - new Country("VU", "바누아투", "🇻🇺"), - new Country("BH", "바레인", "🇧🇭"), - new Country("BB", "바베이도스", "🇧🇧"), - new Country("VA", "바티칸", "🇻🇦"), - new Country("BS", "바하마", "🇧🇸"), - new Country("BD", "방글라데시", "🇧🇩"), - new Country("BM", "버뮤다", "🇧🇲"), - new Country("BJ", "베냉", "🇧🇯"), - new Country("VE", "베네수엘라", "🇻🇪"), - new Country("VN", "베트남", "🇻🇳"), - new Country("BY", "벨라루스", "🇧🇾"), - new Country("BZ", "벨리즈", "🇧🇿"), - new Country("BQ", "보네르섬, 신트유스타티우스섬, 사바섬", "🇧🇶"), - new Country("BA", "보스니아 헤르체고비나", "🇧🇦"), - new Country("BW", "보츠와나", "🇧🇼"), - new Country("BO", "볼리비아", "🇧🇴"), - new Country("BI", "부룬디", "🇧🇮"), - new Country("BF", "부르키나파소", "🇧🇫"), - new Country("BV", "부베섬", "🇧🇻"), - new Country("BT", "부탄", "🇧🇹"), - new Country("MP", "북마리아나제도", "🇲🇵"), - new Country("MK", "북마케도니아", "🇲🇰"), - new Country("BG", "불가리아", "🇧🇬"), - new Country("BR", "브라질", "🇧🇷"), - new Country("BN", "브루나이", "🇧🇳"), - new Country("WS", "사모아", "🇼🇸"), - new Country("SA", "사우디아라비아", "🇸🇦"), - new Country("GS", "사우스조지아 사우스샌드위치 제도", "🇬🇸"), - new Country("SM", "산마리노", "🇸🇲"), - new Country("ST", "상투메 프린시페", "🇸🇹"), - new Country("MF", "생마르탱(프랑스령)", "🇲🇫"), - new Country("BL", "생바르텔레미", "🇧🇱"), - new Country("PM", "생피에르 미클롱", "🇵🇲"), - new Country("EH", "서사하라", "🇪🇭"), - new Country("SN", "세네갈", "🇸🇳"), - new Country("RS", "세르비아", "🇷🇸"), - new Country("SC", "세이셸", "🇸🇨"), - new Country("LC", "세인트루시아", "🇱🇨"), - new Country("VC", "세인트빈센트 그레나딘", "🇻🇨"), - new Country("KN", "세인트키츠 네비스", "🇰🇳"), - new Country("SH", "세인트헬레나", "🇸🇭"), - new Country("SO", "소말리아", "🇸🇴"), - new Country("SB", "솔로몬 제도", "🇸🇧"), - new Country("SD", "수단", "🇸🇩"), - new Country("SR", "수리남", "🇸🇷"), - new Country("LK", "스리랑카", "🇱🇰"), - new Country("SJ", "스발바르 얀마옌 제도", "🇸🇯"), - new Country("SE", "스웨덴", "🇸🇪"), - new Country("CH", "스위스", "🇨🇭"), - new Country("ES", "스페인", "🇪🇸"), - new Country("SK", "슬로바키아", "🇸🇰"), - new Country("SI", "슬로베니아", "🇸🇮"), - new Country("SY", "시리아", "🇸🇾"), - new Country("SL", "시에라리온", "🇸🇱"), - new Country("SX", "신트마르턴", "🇸🇽"), - new Country("SG", "싱가포르", "🇸🇬"), - new Country("AE", "아랍에미리트", "🇦🇪"), - new Country("AW", "아루바", "🇦🇼"), - new Country("AM", "아르메니아", "🇦🇲"), - new Country("AS", "아메리칸사모아", "🇦🇸"), - new Country("IS", "아이슬란드", "🇮🇸"), - new Country("HT", "아이티", "🇭🇹"), - new Country("IE", "아일랜드", "🇮🇪"), - new Country("AZ", "아제르바이잔", "🇦🇿"), - new Country("AF", "아프가니스탄", "🇦🇫"), - new Country("AD", "안도라", "🇦🇩"), - new Country("AG", "안티구아 바부다", "🇦🇬"), - new Country("AL", "알바니아", "🇦🇱"), - new Country("DZ", "알제리", "🇩🇿"), - new Country("AO", "앙골라", "🇦🇴"), - new Country("AI", "앵귈라", "🇦🇮"), - new Country("ER", "에리트레아", "🇪🇷"), - new Country("SZ", "에스와티니", "🇸🇿"), - new Country("EE", "에스토니아", "🇪🇪"), - new Country("EC", "에콰도르", "🇪🇨"), - new Country("ET", "에티오피아", "🇪🇹"), - new Country("SV", "엘살바도르", "🇸🇻"), - new Country("GB", "영국", "🇬🇧"), - new Country("VG", "영국령 버진아일랜드", "🇻🇬"), - new Country("IO", "영국령 인도양 지역", "🇮🇴"), - new Country("YE", "예멘", "🇾🇪"), - new Country("OM", "오만", "🇴🇲"), - new Country("AT", "오스트리아", "🇦🇹"), - new Country("HN", "온두라스", "🇭🇳"), - new Country("AX", "올란드 제도", "🇦🇽"), - new Country("JO", "요르단", "🇯🇴"), - new Country("UG", "우간다", "🇺🇬"), - new Country("UY", "우루과이", "🇺🇾"), - new Country("UZ", "우즈베키스탄", "🇺🇿"), - new Country("UA", "우크라이나", "🇺🇦"), - new Country("WF", "월리스 푸투나", "🇼🇫"), - new Country("IQ", "이라크", "🇮🇶"), - new Country("IR", "이란", "🇮🇷"), - new Country("IL", "이스라엘", "🇮🇱"), - new Country("EG", "이집트", "🇪🇬"), - new Country("IT", "이탈리아", "🇮🇹"), - new Country("IN", "인도", "🇮🇳"), - new Country("ID", "인도네시아", "🇮🇩"), - new Country("JP", "일본", "🇯🇵"), - new Country("JM", "자메이카", "🇯🇲"), - new Country("ZM", "잠비아", "🇿🇲"), - new Country("JE", "저지", "🇯🇪"), - new Country("GQ", "적도 기니", "🇬🇶"), - new Country("KP", "조선민주주의인민공화국", "🇰🇵"), - new Country("GE", "조지아", "🇬🇪"), - new Country("CN", "중국", "🇨🇳"), - new Country("CF", "중앙아프리카공화국", "🇨🇫"), - new Country("DJ", "지부티", "🇩🇯"), - new Country("GI", "지브롤터", "🇬🇮"), - new Country("ZW", "짐바브웨", "🇿🇼"), - new Country("TD", "차드", "🇹🇩"), - new Country("CZ", "체코", "🇨🇿"), - new Country("CL", "칠레", "🇨🇱"), - new Country("CM", "카메룬", "🇨🇲"), - new Country("CV", "카보베르데", "🇨🇻"), - new Country("KZ", "카자흐스탄", "🇰🇿"), - new Country("QA", "카타르", "🇶🇦"), - new Country("KH", "캄보디아", "🇰🇭"), - new Country("CA", "캐나다", "🇨🇦"), - new Country("KE", "케냐", "🇰🇪"), - new Country("KY", "케이맨 제도", "🇰🇾"), - new Country("KM", "코모로", "🇰🇲"), - new Country("CR", "코스타리카", "🇨🇷"), - new Country("CC", "코코스(킬링) 제도", "🇨🇨"), - new Country("CI", "코트디부아르", "🇨🇮"), - new Country("CO", "콜롬비아", "🇨🇴"), - new Country("CG", "콩고", "🇨🇬"), - new Country("CD", "콩고민주공화국", "🇨🇩"), - new Country("CU", "쿠바", "🇨🇺"), - new Country("KW", "쿠웨이트", "🇰🇼"), - new Country("CK", "쿡 제도", "🇨🇰"), - new Country("CW", "퀴라소", "🇨🇼"), - new Country("HR", "크로아티아", "🇭🇷"), - new Country("CX", "크리스마스섬", "🇨🇽"), - new Country("KG", "키르기스스탄", "🇰🇬"), - new Country("KI", "키리바시", "🇰🇮"), - new Country("CY", "키프로스", "🇨🇾"), - new Country("TJ", "타지키스탄", "🇹🇯"), - new Country("TZ", "탄자니아", "🇹🇿"), - new Country("TH", "태국", "🇹🇭"), - new Country("TC", "터크스 케이커스 제도", "🇹🇨"), - new Country("TR", "터키", "🇹🇷"), - new Country("TG", "토고", "🇹🇬"), - new Country("TK", "토켈라우", "🇹🇰"), - new Country("TO", "통가", "🇹🇴"), - new Country("TM", "투르크메니스탄", "🇹🇲"), - new Country("TV", "투발루", "🇹🇻"), - new Country("TN", "튀니지", "🇹🇳"), - new Country("TT", "트리니다드 토바고", "🇹🇹"), - new Country("PA", "파나마", "🇵🇦"), - new Country("PY", "파라과이", "🇵🇾"), - new Country("PK", "파키스탄", "🇵🇰"), - new Country("PG", "파푸아뉴기니", "🇵🇬"), - new Country("PW", "팔라우", "🇵🇼"), - new Country("PS", "팔레스타인", "🇵🇸"), - new Country("FO", "페로 제도", "🇫🇴"), - new Country("PE", "페루", "🇵🇪"), - new Country("PT", "포르투갈", "🇵🇹"), - new Country("FK", "포클랜드 제도", "🇫🇰"), - new Country("PL", "폴란드", "🇵🇱"), - new Country("PR", "푸에르토리코", "🇵🇷"), - new Country("FR", "프랑스", "🇫🇷"), - new Country("GF", "프랑스령 기아나", "🇬🇫"), - new Country("TF", "프랑스령 남부 지역", "🇹🇫"), - new Country("PF", "프랑스령 폴리네시아", "🇵🇫"), - new Country("FJ", "피지", "🇫🇯"), - new Country("FI", "핀란드", "🇫🇮"), - new Country("PH", "필리핀", "🇵🇭"), - new Country("PN", "핏케언 제도", "🇵🇳"), - new Country("HM", "허드 맥도널드 제도", "🇭🇲"), - new Country("HU", "헝가리", "🇭🇺"), - new Country("HK", "홍콩", "🇭🇰") - ); - countries.forEach(countryRepository::save); - countryRepository.flush(); // 강제 DB 반영 - - List check = countryRepository.findAll(); - log.info("실제 저장된 Country 수: {}", check.size()); - } -} +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql new file mode 100644 index 0000000..9c8276f --- /dev/null +++ b/src/main/resources/db/migration/V1__init.sql @@ -0,0 +1,169 @@ +-- ============================================ +-- V1__init.sql +-- 초기 데이터베이스 스키마 생성 +-- ============================================ + +-- Country 테이블 (FK 없음, 가장 먼저 생성) +CREATE TABLE IF NOT EXISTS `country` ( + `countryCode` VARCHAR(2) NOT NULL PRIMARY KEY, + `countryName` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `emoji` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Emotion 테이블 (FK 없음) +CREATE TABLE IF NOT EXISTS `emotion` ( + `emotion_id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `colorCode` VARCHAR(255) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Weather 테이블 (FK 없음) +CREATE TABLE IF NOT EXISTS `weather` ( + `weather_id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `iconUrl` VARCHAR(255) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- TripTheme 테이블 (FK 없음) +CREATE TABLE IF NOT EXISTS `trip_theme` ( + `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `themeName` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `sampleImageUrl` VARCHAR(255), + `cardImageUrl` VARCHAR(255) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- BoardTheme 테이블 (FK 없음) +CREATE TABLE IF NOT EXISTS `board_theme` ( + `boardThemeId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `themeName` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `thumbnailUrl` VARCHAR(255) NOT NULL, + `cardUrl` VARCHAR(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Sticker 테이블 (FK 없음) +CREATE TABLE IF NOT EXISTS `sticker` ( + `stickerId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `imageUrl` VARCHAR(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- User 테이블 (FK 없음) +CREATE TABLE IF NOT EXISTS `user` ( + `userId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `kakaoId` VARCHAR(255) UNIQUE, + `profileImageUrl` VARCHAR(255), + `sur_name` VARCHAR(255), + `first_name` VARCHAR(255), + `korean_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `createdAt` DATE DEFAULT (CURRENT_DATE), + `status` BOOLEAN DEFAULT FALSE, + `birth` DATE, + `nationality` VARCHAR(255), + `isRegistered` BOOLEAN NOT NULL DEFAULT FALSE, + `diaryCount` BIGINT, + `visitedCountryCount` BIGINT, + `flags` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Setting 테이블 (FK: User) +CREATE TABLE IF NOT EXISTS `setting` ( + `userId` BIGINT NOT NULL PRIMARY KEY, + `alarm` BOOLEAN NOT NULL DEFAULT TRUE, + CONSTRAINT `fk_setting_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Trip 테이블 (FK: User, TripTheme) +CREATE TABLE IF NOT EXISTS `trip` ( + `tripId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `tripName` VARCHAR(14) NOT NULL, + `description` VARCHAR(56), + `startDate` DATE NOT NULL, + `endDate` DATE NOT NULL, + `isStored` BOOLEAN DEFAULT FALSE, + `isPast` BOOLEAN DEFAULT FALSE, + `userId` BIGINT, + `tripThemeId` BIGINT, + `content` LONGTEXT, + `representativeImageUrl` VARCHAR(255), + CONSTRAINT `fk_trip_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_trip_trip_theme` FOREIGN KEY (`tripThemeId`) REFERENCES `trip_theme` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Diary 테이블 (FK: User, Trip, Country, Emotion, Weather) +CREATE TABLE IF NOT EXISTS `diary` ( + `diaryId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `userId` BIGINT, + `tripId` BIGINT, + `countryId` VARCHAR(2), + `city` VARCHAR(255) NOT NULL, + `dateTime` DATETIME(6) NOT NULL, + `content` LONGTEXT NOT NULL, + `detailedLocation` VARCHAR(255), + `emotionId` BIGINT, + `weatherId` BIGINT, + `createdAt` DATETIME(6) DEFAULT (UTC_TIMESTAMP()), + `isStored` BOOLEAN NOT NULL DEFAULT FALSE, + CONSTRAINT `fk_diary_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_diary_trip` FOREIGN KEY (`tripId`) REFERENCES `trip` (`tripId`) ON DELETE CASCADE, + CONSTRAINT `fk_diary_country` FOREIGN KEY (`countryId`) REFERENCES `country` (`countryCode`), + CONSTRAINT `fk_diary_emotion` FOREIGN KEY (`emotionId`) REFERENCES `emotion` (`emotion_id`), + CONSTRAINT `fk_diary_weather` FOREIGN KEY (`weatherId`) REFERENCES `weather` (`weather_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- DiaryImage 테이블 (FK: Diary) +CREATE TABLE IF NOT EXISTS `diary_image` ( + `DiaryImageId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `diaryId` BIGINT, + `imageUrl` VARCHAR(255) NOT NULL, + `cameraType` VARCHAR(255) NOT NULL, + `isRepresentative` BOOLEAN DEFAULT FALSE, + `imageOrder` INT, + CONSTRAINT `fk_diary_image_diary` FOREIGN KEY (`diaryId`) REFERENCES `diary` (`diaryId`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- VisitedCountry 테이블 (FK: User, Country, Emotion) +CREATE TABLE IF NOT EXISTS `visited_country` ( + `visitedCountryId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `color` VARCHAR(255), + `createdAt` DATETIME(6) DEFAULT (UTC_TIMESTAMP()), + `userId` BIGINT, + `countryCode` VARCHAR(2), + `emotionId` BIGINT, + CONSTRAINT `fk_visited_country_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_visited_country_country` FOREIGN KEY (`countryCode`) REFERENCES `country` (`countryCode`), + CONSTRAINT `fk_visited_country_emotion` FOREIGN KEY (`emotionId`) REFERENCES `emotion` (`emotion_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Board 테이블 (FK: User, BoardTheme) +CREATE TABLE IF NOT EXISTS `board` ( + `boardId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `title` VARCHAR(100) NOT NULL, + `createdAt` DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP()), + `updatedAt` DATETIME(6), + `userId` BIGINT NOT NULL, + `boardThemeId` BIGINT NOT NULL, + CONSTRAINT `fk_board_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_board_board_theme` FOREIGN KEY (`boardThemeId`) REFERENCES `board_theme` (`boardThemeId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- BoardStickerMap 테이블 (FK: Board, Sticker) +CREATE TABLE IF NOT EXISTS `board_sticker_map` ( + `boardStickerId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `boardId` BIGINT NOT NULL, + `stickerId` BIGINT NOT NULL, + `posX` DECIMAL(19,2) NOT NULL, + `posY` DECIMAL(19,2) NOT NULL, + `rotation` DECIMAL(19,2) NOT NULL, + CONSTRAINT `fk_board_sticker_map_board` FOREIGN KEY (`boardId`) REFERENCES `board` (`boardId`) ON DELETE CASCADE, + CONSTRAINT `fk_board_sticker_map_sticker` FOREIGN KEY (`stickerId`) REFERENCES `sticker` (`stickerId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- AlarmHistory 테이블 (FK 없음, 하지만 User, Trip 참조) +CREATE TABLE IF NOT EXISTS `alarm_history` ( + `alarm_history_id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `userId` BIGINT NOT NULL, + `tripId` BIGINT NOT NULL, + `alarmType` VARCHAR(50) NOT NULL, + `sentAt` DATETIME NOT NULL, + `sentDate` DATE NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/src/main/resources/db/migration/V2__insert_basic_data.sql b/src/main/resources/db/migration/V2__insert_basic_data.sql new file mode 100644 index 0000000..4fc7f72 --- /dev/null +++ b/src/main/resources/db/migration/V2__insert_basic_data.sql @@ -0,0 +1,48 @@ +-- ============================================ +-- V2__insert_basic_data.sql +-- 기초 데이터 삽입 (TripTheme, Emotion, Weather, BoardTheme, Sticker) +-- ============================================ + +-- TripTheme 데이터 삽입 +INSERT INTO `trip_theme` (`themeName`, `sampleImageUrl`, `cardImageUrl`) VALUES +('기본', 'https://me-mory01.mooo.com/api/files?key=images/d69be93f-fef6-4a73-815f-ea6b93fd2d59_trip_thumb_default.png', 'https://me-mory01.mooo.com/api/files?key=images/6e56681d-9fc3-43d2-a803-0ef165c63c3c_trip_card_default.png'), +('Grey', 'https://me-mory01.mooo.com/api/files?key=images/f0d5e1f7-2718-4dac-a780-d6f6e5f2d42a_trip_thumb_grey.png', 'https://me-mory01.mooo.com/api/files?key=images/462d91ad-b618-4449-805f-35f07187247b_trip_card_grey.png'), +('탑승권', 'https://me-mory01.mooo.com/api/files?key=images/cbcf48b8-8469-4e0f-b597-7ef27cc6190e_trip_thumb_boardingpass.png', 'https://me-mory01.mooo.com/api/files?key=images/3e637461-f9a2-477d-ae06-4245033174a9_trip_card_boardingpass.png'), +('액자', 'https://me-mory01.mooo.com/api/files?key=images/c3efb8f8-f4a8-41a1-9d29-e93bc9d2dd83_trip_thumb_frame.png', 'https://me-mory01.mooo.com/api/files?key=images/eade6dc1-c412-4924-af7f-832292b258c0_trip_card_frame.png'), +('Beach', 'https://me-mory01.mooo.com/api/files?key=images/f692134b-0676-47e9-9782-778f7df9a23a_trip_thumb_beach.png', 'https://me-mory01.mooo.com/api/files?key=images/94abb8d5-5f56-4061-8e90-1df9c9107caf_trip_card_beach.png'), +('Forest', 'https://me-mory01.mooo.com/api/files?key=images/6f40c3c9-313f-44e5-9624-3264991b2f9b_trip_thumb_forest.png', 'https://me-mory01.mooo.com/api/files?key=images/99589e53-9539-46eb-b702-644fab7a87e3_trip_card_forest.png'); + +-- Emotion 데이터 삽입 +INSERT INTO `emotion` (`name`, `colorCode`) VALUES +('기본', '#EEEEEE'), +('설렘', '#FDD7DE'), +('신기함', '#FFCB6B'), +('즐거움', '#FFE13E'), +('힐링', '#C1E8A0'), +('평온', '#D1E5D4'), +('뿌듯함', '#5ACFD5'), +('해방감', '#5EB6D9'), +('낯섦', '#634E72'), +('긴장됨', '#2C3E50'), +('외로움', '#A9A9B0'), +('아쉬움', '#866868'), +('벅참', '#800020'); + +-- Weather 데이터 삽입 +INSERT INTO `weather` (`name`, `iconUrl`) VALUES +('맑음', 'https://me-mory01.mooo.com/api/files?key=images/7de47dfb-6f09-47d2-9268-2b8c49c2f9bd_weather_sunny.png'), +('구름', 'https://me-mory01.mooo.com/api/files?key=images/6a8c4a38-0ccd-479c-ba70-7202d7535bf7_weather_cloudy.png'), +('비', 'https://me-mory01.mooo.com/api/files?key=images/72a8e191-9b31-4646-ae48-cb31fb543de7_weather_rainy.png'), +('바람', 'https://me-mory01.mooo.com/api/files?key=images/dae2b4e2-7ac4-45fe-bccc-be835218d8d4_weather_windy.png'), +('눈', 'https://me-mory01.mooo.com/api/files?key=images/3b298957-360e-4ea4-86ea-bcd65fcd3f77_weather_snowy.png'); + +-- BoardTheme 데이터 삽입 +INSERT INTO `board_theme` (`themeName`, `thumbnailUrl`, `cardUrl`) VALUES +('칠판', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/chalkboard_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/73212582-a940-4929-9c9b-f99ce68f3238_board_card_chalkboard.png'), +('식탁보', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/tablecloth_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/ea8230ad-a70a-4f87-acb4-648add56b1b8_board_card_tablecloth.png'), +('나무보드', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/board_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/ecee4553-72cc-4097-8d20-10a7ee889269_board_card_woodboard.png'), +('체스판', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/chess_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/e026afe7-1619-4ee5-b3e7-c2564d88447c_board_card_chess.png'); + +-- Sticker 데이터 삽입 +INSERT INTO `sticker` (`name`, `imageUrl`) VALUES +('Star', 'https://me-mory01.mooo.com/api/files?key=images/6d4c8b2f-00ec-41ae-bd57-9ce914e1c78d_sticker_star.png'); diff --git a/src/main/resources/db/migration/V3__insert_countries.sql b/src/main/resources/db/migration/V3__insert_countries.sql new file mode 100644 index 0000000..c83ad84 --- /dev/null +++ b/src/main/resources/db/migration/V3__insert_countries.sql @@ -0,0 +1,251 @@ +-- ============================================ +-- V3__insert_countries.sql +-- Country 데이터 삽입 (245개 국가) +-- ============================================ + +INSERT INTO `country` (`countryCode`, `countryName`, `emoji`) VALUES +('GH', '가나', '🇬🇭'), +('GA', '가봉', '🇬🇦'), +('GY', '가이아나', '🇬🇾'), +('GM', '감비아', '🇬🇲'), +('GG', '건지', '🇬🇬'), +('GP', '과들루프', '🇬🇵'), +('GT', '과테말라', '🇬🇹'), +('GU', '괌', '🇬🇺'), +('GD', '그레나다', '🇬🇩'), +('GR', '그리스', '🇬🇷'), +('GL', '그린란드', '🇬🇱'), +('GN', '기니', '🇬🇳'), +('GW', '기니비사우', '🇬🇼'), +('NA', '나미비아', '🇳🇦'), +('NR', '나우루', '🇳🇷'), +('NG', '나이지리아', '🇳🇬'), +('AQ', '남극', '🇦🇶'), +('SS', '남수단', '🇸🇸'), +('ZA', '남아프리카공화국', '🇿🇦'), +('NL', '네덜란드', '🇳🇱'), +('NP', '네팔', '🇳🇵'), +('NO', '노르웨이', '🇳🇴'), +('NF', '노퍽섬', '🇳🇫'), +('NZ', '뉴질랜드', '🇳🇿'), +('NC', '뉴칼레도니아', '🇳🇨'), +('NU', '니우에', '🇳🇺'), +('NE', '니제르', '🇳🇪'), +('NI', '니카라과', '🇳🇮'), +('TW', '대만', '🇹🇼'), +('KR', '대한민국', '🇰🇷'), +('DK', '덴마크', '🇩🇰'), +('DM', '도미니카', '🇩🇲'), +('DO', '도미니카 공화국', '🇩🇴'), +('DE', '독일', '🇩🇪'), +('TL', '동티모르', '🇹🇱'), +('LA', '라오스', '🇱🇦'), +('LR', '라이베리아', '🇱🇷'), +('LV', '라트비아', '🇱🇻'), +('RU', '러시아', '🇷🇺'), +('LB', '레바논', '🇱🇧'), +('LS', '레소토', '🇱🇸'), +('RE', '레위니옹', '🇷🇪'), +('RO', '루마니아', '🇷🇴'), +('LU', '룩셈부르크', '🇱🇺'), +('RW', '르완다', '🇷🇼'), +('LY', '리비아', '🇱🇾'), +('LT', '리투아니아', '🇱🇹'), +('LI', '리히텐슈타인', '🇱🇮'), +('MG', '마다가스카르', '🇲🇬'), +('MQ', '마르티니크', '🇲🇶'), +('MH', '마셜제도', '🇲🇭'), +('YT', '마요트', '🇾🇹'), +('MO', '마카오', '🇲🇴'), +('MW', '말라위', '🇲🇼'), +('MY', '말레이시아', '🇲🇾'), +('ML', '말리', '🇲🇱'), +('IM', '맨섬', '🇮🇲'), +('MX', '멕시코', '🇲🇽'), +('MC', '모나코', '🇲🇨'), +('MA', '모로코', '🇲🇦'), +('MR', '모리타니', '🇲🇷'), +('MZ', '모잠비크', '🇲🇿'), +('ME', '몬테네그로', '🇲🇪'), +('MS', '몬트세라트', '🇲🇸'), +('MD', '몰도바', '🇲🇩'), +('MV', '몰디브', '🇲🇻'), +('MT', '몰타', '🇲🇹'), +('MN', '몽골', '🇲🇳'), +('US', '미국', '🇺🇸'), +('UM', '미국령 군소 제도', '🇺🇲'), +('VI', '미국령 버진아일랜드', '🇻🇮'), +('MM', '미얀마', '🇲🇲'), +('FM', '미크로네시아', '🇫🇲'), +('VU', '바누아투', '🇻🇺'), +('BH', '바레인', '🇧🇭'), +('BB', '바베이도스', '🇧🇧'), +('VA', '바티칸', '🇻🇦'), +('BS', '바하마', '🇧🇸'), +('BD', '방글라데시', '🇧🇩'), +('BM', '버뮤다', '🇧🇲'), +('BJ', '베냉', '🇧🇯'), +('VE', '베네수엘라', '🇻🇪'), +('VN', '베트남', '🇻🇳'), +('BY', '벨라루스', '🇧🇾'), +('BZ', '벨리즈', '🇧🇿'), +('BQ', '보네르섬, 신트유스타티우스섬, 사바섬', '🇧🇶'), +('BA', '보스니아 헤르체고비나', '🇧🇦'), +('BW', '보츠와나', '🇧🇼'), +('BO', '볼리비아', '🇧🇴'), +('BI', '부룬디', '🇧🇮'), +('BF', '부르키나파소', '🇧🇫'), +('BV', '부베섬', '🇧🇻'), +('BT', '부탄', '🇧🇹'), +('MP', '북마리아나제도', '🇲🇵'), +('MK', '북마케도니아', '🇲🇰'), +('BG', '불가리아', '🇧🇬'), +('BR', '브라질', '🇧🇷'), +('BN', '브루나이', '🇧🇳'), +('WS', '사모아', '🇼🇸'), +('SA', '사우디아라비아', '🇸🇦'), +('GS', '사우스조지아 사우스샌드위치 제도', '🇬🇸'), +('SM', '산마리노', '🇸🇲'), +('ST', '상투메 프린시페', '🇸🇹'), +('MF', '생마르탱(프랑스령)', '🇲🇫'), +('BL', '생바르텔레미', '🇧🇱'), +('PM', '생피에르 미클롱', '🇵🇲'), +('EH', '서사하라', '🇪🇭'), +('SN', '세네갈', '🇸🇳'), +('RS', '세르비아', '🇷🇸'), +('SC', '세이셸', '🇸🇨'), +('LC', '세인트루시아', '🇱🇨'), +('VC', '세인트빈센트 그레나딘', '🇻🇨'), +('KN', '세인트키츠 네비스', '🇰🇳'), +('SH', '세인트헬레나', '🇸🇭'), +('SO', '소말리아', '🇸🇴'), +('SB', '솔로몬 제도', '🇸🇧'), +('SD', '수단', '🇸🇩'), +('SR', '수리남', '🇸🇷'), +('LK', '스리랑카', '🇱🇰'), +('SJ', '스발바르 얀마옌 제도', '🇸🇯'), +('SE', '스웨덴', '🇸🇪'), +('CH', '스위스', '🇨🇭'), +('ES', '스페인', '🇪🇸'), +('SK', '슬로바키아', '🇸🇰'), +('SI', '슬로베니아', '🇸🇮'), +('SY', '시리아', '🇸🇾'), +('SL', '시에라리온', '🇸🇱'), +('SX', '신트마르턴', '🇸🇽'), +('SG', '싱가포르', '🇸🇬'), +('AE', '아랍에미리트', '🇦🇪'), +('AW', '아루바', '🇦🇼'), +('AM', '아르메니아', '🇦🇲'), +('AS', '아메리칸사모아', '🇦🇸'), +('IS', '아이슬란드', '🇮🇸'), +('HT', '아이티', '🇭🇹'), +('IE', '아일랜드', '🇮🇪'), +('AZ', '아제르바이잔', '🇦🇿'), +('AF', '아프가니스탄', '🇦🇫'), +('AD', '안도라', '🇦🇩'), +('AG', '안티구아 바부다', '🇦🇬'), +('AL', '알바니아', '🇦🇱'), +('DZ', '알제리', '🇩🇿'), +('AO', '앙골라', '🇦🇴'), +('AI', '앵귈라', '🇦🇮'), +('ER', '에리트레아', '🇪🇷'), +('SZ', '에스와티니', '🇸🇿'), +('EE', '에스토니아', '🇪🇪'), +('EC', '에콰도르', '🇪🇨'), +('ET', '에티오피아', '🇪🇹'), +('SV', '엘살바도르', '🇸🇻'), +('GB', '영국', '🇬🇧'), +('VG', '영국령 버진아일랜드', '🇻🇬'), +('IO', '영국령 인도양 지역', '🇮🇴'), +('YE', '예멘', '🇾🇪'), +('OM', '오만', '🇴🇲'), +('AT', '오스트리아', '🇦🇹'), +('HN', '온두라스', '🇭🇳'), +('AX', '올란드 제도', '🇦🇽'), +('JO', '요르단', '🇯🇴'), +('UG', '우간다', '🇺🇬'), +('UY', '우루과이', '🇺🇾'), +('UZ', '우즈베키스탄', '🇺🇿'), +('UA', '우크라이나', '🇺🇦'), +('WF', '월리스 푸투나', '🇼🇫'), +('IQ', '이라크', '🇮🇶'), +('IR', '이란', '🇮🇷'), +('IL', '이스라엘', '🇮🇱'), +('EG', '이집트', '🇪🇬'), +('IT', '이탈리아', '🇮🇹'), +('IN', '인도', '🇮🇳'), +('ID', '인도네시아', '🇮🇩'), +('JP', '일본', '🇯🇵'), +('JM', '자메이카', '🇯🇲'), +('ZM', '잠비아', '🇿🇲'), +('JE', '저지', '🇯🇪'), +('GQ', '적도 기니', '🇬🇶'), +('KP', '조선민주주의인민공화국', '🇰🇵'), +('GE', '조지아', '🇬🇪'), +('CN', '중국', '🇨🇳'), +('CF', '중앙아프리카공화국', '🇨🇫'), +('DJ', '지부티', '🇩🇯'), +('GI', '지브롤터', '🇬🇮'), +('ZW', '짐바브웨', '🇿🇼'), +('TD', '차드', '🇹🇩'), +('CZ', '체코', '🇨🇿'), +('CL', '칠레', '🇨🇱'), +('CM', '카메룬', '🇨🇲'), +('CV', '카보베르데', '🇨🇻'), +('KZ', '카자흐스탄', '🇰🇿'), +('QA', '카타르', '🇶🇦'), +('KH', '캄보디아', '🇰🇭'), +('CA', '캐나다', '🇨🇦'), +('KE', '케냐', '🇰🇪'), +('KY', '케이맨 제도', '🇰🇾'), +('KM', '코모로', '🇰🇲'), +('CR', '코스타리카', '🇨🇷'), +('CC', '코코스(킬링) 제도', '🇨🇨'), +('CI', '코트디부아르', '🇨🇮'), +('CO', '콜롬비아', '🇨🇴'), +('CG', '콩고', '🇨🇬'), +('CD', '콩고민주공화국', '🇨🇩'), +('CU', '쿠바', '🇨🇺'), +('KW', '쿠웨이트', '🇰🇼'), +('CK', '쿡 제도', '🇨🇰'), +('CW', '퀴라소', '🇨🇼'), +('HR', '크로아티아', '🇭🇷'), +('CX', '크리스마스섬', '🇨🇽'), +('KG', '키르기스스탄', '🇰🇬'), +('KI', '키리바시', '🇰🇮'), +('CY', '키프로스', '🇨🇾'), +('TJ', '타지키스탄', '🇹🇯'), +('TZ', '탄자니아', '🇹🇿'), +('TH', '태국', '🇹🇭'), +('TC', '터크스 케이커스 제도', '🇹🇨'), +('TR', '터키', '🇹🇷'), +('TG', '토고', '🇹🇬'), +('TK', '토켈라우', '🇹🇰'), +('TO', '통가', '🇹🇴'), +('TM', '투르크메니스탄', '🇹🇲'), +('TV', '투발루', '🇹🇻'), +('TN', '튀니지', '🇹🇳'), +('TT', '트리니다드 토바고', '🇹🇹'), +('PA', '파나마', '🇵🇦'), +('PY', '파라과이', '🇵🇾'), +('PK', '파키스탄', '🇵🇰'), +('PG', '파푸아뉴기니', '🇵🇬'), +('PW', '팔라우', '🇵🇼'), +('PS', '팔레스타인', '🇵🇸'), +('FO', '페로 제도', '🇫🇴'), +('PE', '페루', '🇵🇪'), +('PT', '포르투갈', '🇵🇹'), +('FK', '포클랜드 제도', '🇫🇰'), +('PL', '폴란드', '🇵🇱'), +('PR', '푸에르토리코', '🇵🇷'), +('FR', '프랑스', '🇫🇷'), +('GF', '프랑스령 기아나', '🇬🇫'), +('TF', '프랑스령 남부 지역', '🇹🇫'), +('PF', '프랑스령 폴리네시아', '🇵🇫'), +('FJ', '피지', '🇫🇯'), +('FI', '핀란드', '🇫🇮'), +('PH', '필리핀', '🇵🇭'), +('PN', '핏케언 제도', '🇵🇳'), +('HM', '허드 맥도널드 제도', '🇭🇲'), +('HU', '헝가리', '🇭🇺'), +('HK', '홍콩', '🇭🇰'); diff --git a/src/main/resources/db/migration/V4__test_user.sql b/src/main/resources/db/migration/V4__test_user.sql new file mode 100644 index 0000000..bf2dea5 --- /dev/null +++ b/src/main/resources/db/migration/V4__test_user.sql @@ -0,0 +1,45 @@ +-- ============================================ +-- V4__test_user.sql +-- 개발 환경 전용 테스트 사용자 데이터 삽입 +-- 운영 환경에서는 실행되지 않음 (application-prod.yml에서 locations 제한 또는 수동 제외) +-- ============================================ + +-- 테스트 사용자 삽입 (이미 존재하는 경우 무시) +INSERT IGNORE INTO `user` ( + `kakaoId`, + `profileImageUrl`, + `sur_name`, + `first_name`, + `korean_name`, + `birth`, + `nationality`, + `createdAt`, + `status`, + `isRegistered`, + `diaryCount`, + `visitedCountryCount`, + `flags` +) VALUES ( + 'test_강지혜', + 'https://me-mory.mooo.com/api/files?key=images/7c24aa61-2d36-48fd-80a5-646ac518256d_IMG_3831.jpg', + 'KANG', + 'JIHYE', + '강지혜', + '1995-03-15', + 'REPUBLIC OF KOREA', + CURRENT_DATE, + TRUE, + FALSE, + 0, + 0, + '🇰🇷' +); + +-- 테스트 사용자의 Setting 생성 (이미 존재하는 경우 무시) +INSERT IGNORE INTO `setting` (`userId`, `alarm`) +SELECT u.`userId`, TRUE +FROM `user` u +WHERE u.`kakaoId` = 'test_강지혜' +AND NOT EXISTS ( + SELECT 1 FROM `setting` s WHERE s.`userId` = u.`userId` +); From e0fe12f3942c29f0619720d0c2ec9a376d26db99 Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Mon, 2 Feb 2026 14:19:40 +0900 Subject: [PATCH 03/12] chore: Update application-prod.yml with comments on test data migration --- src/main/resources/application-prod.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 3216900..cf28d38 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -10,6 +10,10 @@ spring: enabled: true baseline-on-migrate: true validate-on-migrate: true + # 운영 환경에서는 테스트 데이터 마이그레이션 제외 + # V4__test_user.sql은 개발 환경에서만 실행됨 + # 주의: 운영 환경 배포 시 V4__test_user.sql을 수동으로 제외하거나 + # locations를 제한하여 V1~V3까지만 실행하도록 설정 필요 memory: base-url: https://voyame-studio.org From 0294a985947f841de6cd0b328693974f886e958f Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Mon, 2 Feb 2026 14:38:50 +0900 Subject: [PATCH 04/12] =?UTF-8?q?refactor=20:=20prod=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EA=B3=BC=20=EA=B0=9C=EB=B0=9C=20=ED=99=98=EA=B2=BD=20=EA=B5=AC?= =?UTF-8?q?=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yml | 6 ++---- src/main/resources/application.yml | 5 ++++- .../db/{migration => migration-dev}/V4__test_user.sql | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) rename src/main/resources/db/{migration => migration-dev}/V4__test_user.sql (90%) diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index cf28d38..e509da4 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -10,10 +10,8 @@ spring: enabled: true baseline-on-migrate: true validate-on-migrate: true - # 운영 환경에서는 테스트 데이터 마이그레이션 제외 - # V4__test_user.sql은 개발 환경에서만 실행됨 - # 주의: 운영 환경 배포 시 V4__test_user.sql을 수동으로 제외하거나 - # locations를 제한하여 V1~V3까지만 실행하도록 설정 필요 + # 운영 환경: db/migration만 실행 (테스트 데이터 제외) + locations: classpath:db/migration memory: base-url: https://voyame-studio.org diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8ab62d3..90ca020 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -43,7 +43,10 @@ spring: enabled: true baseline-on-migrate: false validate-on-migrate: true - locations: classpath:db/migration + # 개발 환경: db/migration + db/migration-dev (테스트 데이터 포함) + locations: + - classpath:db/migration + - classpath:db/migration-dev logging: level: diff --git a/src/main/resources/db/migration/V4__test_user.sql b/src/main/resources/db/migration-dev/V4__test_user.sql similarity index 90% rename from src/main/resources/db/migration/V4__test_user.sql rename to src/main/resources/db/migration-dev/V4__test_user.sql index bf2dea5..fbdf614 100644 --- a/src/main/resources/db/migration/V4__test_user.sql +++ b/src/main/resources/db/migration-dev/V4__test_user.sql @@ -1,7 +1,8 @@ -- ============================================ -- V4__test_user.sql -- 개발 환경 전용 테스트 사용자 데이터 삽입 --- 운영 환경에서는 실행되지 않음 (application-prod.yml에서 locations 제한 또는 수동 제외) +-- 운영 환경에서는 실행되지 않음 (application-prod.yml에서 db/migration만 로드) +-- INSERT IGNORE를 사용하여 이미 데이터가 있으면 실행하지 않음 -- ============================================ -- 테스트 사용자 삽입 (이미 존재하는 경우 무시) From 2dda20f4c1f47c7f8f3ccc7e9746f4dcc332b079 Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Mon, 2 Feb 2026 14:44:50 +0900 Subject: [PATCH 05/12] =?UTF-8?q?chore=20:=20=EC=95=88=20=EC=93=B0?= =?UTF-8?q?=EB=8A=94=20import=EB=AC=B8=20=EC=A0=95=EB=A6=AC=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/zim/tave/memory/controller/AlarmController.java | 2 -- src/main/java/zim/tave/memory/service/S3Uploader.java | 1 - src/main/java/zim/tave/memory/service/SettingService.java | 3 --- src/main/java/zim/tave/memory/util/EmojiValidator.java | 2 -- src/test/java/zim/tave/memory/api/ApiTestBase.java | 1 - .../java/zim/tave/memory/controller/CountryControllerTest.java | 1 - .../java/zim/tave/memory/controller/JoinControllerTest.java | 1 - .../zim/tave/memory/controller/TimelineControllerTest.java | 1 - .../tave/memory/integration/ImageUploadIntegrationTest.java | 1 - .../java/zim/tave/memory/integration/IntegrationTestBase.java | 1 - .../zim/tave/memory/integration/MyPageIntegrationTest.java | 2 -- .../java/zim/tave/memory/service/AlarmPolicyServiceTest.java | 1 - src/test/java/zim/tave/memory/service/JoinServiceTest.java | 1 - src/test/java/zim/tave/memory/service/MyPageServiceTest.java | 1 - 14 files changed, 19 deletions(-) diff --git a/src/main/java/zim/tave/memory/controller/AlarmController.java b/src/main/java/zim/tave/memory/controller/AlarmController.java index 92414c5..5a540c0 100644 --- a/src/main/java/zim/tave/memory/controller/AlarmController.java +++ b/src/main/java/zim/tave/memory/controller/AlarmController.java @@ -17,14 +17,12 @@ import org.springframework.web.bind.annotation.RestController; import zim.tave.memory.config.swagger.ApiErrorCodeExamples; import zim.tave.memory.domain.AlarmType; -import zim.tave.memory.domain.User; import zim.tave.memory.dto.response.AlarmSendResponseDto; import zim.tave.memory.global.common.ApiResponseDto; import zim.tave.memory.global.common.ResponseCode; import zim.tave.memory.global.common.exception.ErrorCode; import zim.tave.memory.service.AlarmService; -import java.util.Map; @RestController @RequiredArgsConstructor diff --git a/src/main/java/zim/tave/memory/service/S3Uploader.java b/src/main/java/zim/tave/memory/service/S3Uploader.java index e91f75e..8613fcf 100644 --- a/src/main/java/zim/tave/memory/service/S3Uploader.java +++ b/src/main/java/zim/tave/memory/service/S3Uploader.java @@ -6,7 +6,6 @@ import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.ObjectCannedACL; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import zim.tave.memory.config.MemoryProperties; diff --git a/src/main/java/zim/tave/memory/service/SettingService.java b/src/main/java/zim/tave/memory/service/SettingService.java index eefe17c..adf09de 100644 --- a/src/main/java/zim/tave/memory/service/SettingService.java +++ b/src/main/java/zim/tave/memory/service/SettingService.java @@ -13,9 +13,6 @@ import zim.tave.memory.repository.UserRepository; import zim.tave.memory.repository.VisitedCountryRepository; -import java.util.List; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor public class SettingService { diff --git a/src/main/java/zim/tave/memory/util/EmojiValidator.java b/src/main/java/zim/tave/memory/util/EmojiValidator.java index 6aa35f9..0ae13e3 100644 --- a/src/main/java/zim/tave/memory/util/EmojiValidator.java +++ b/src/main/java/zim/tave/memory/util/EmojiValidator.java @@ -1,7 +1,5 @@ package zim.tave.memory.util; -import java.util.regex.Pattern; - public class EmojiValidator { private EmojiValidator() { diff --git a/src/test/java/zim/tave/memory/api/ApiTestBase.java b/src/test/java/zim/tave/memory/api/ApiTestBase.java index 43dd6c3..c184968 100644 --- a/src/test/java/zim/tave/memory/api/ApiTestBase.java +++ b/src/test/java/zim/tave/memory/api/ApiTestBase.java @@ -14,7 +14,6 @@ import java.time.LocalDate; import java.time.OffsetDateTime; -import java.time.ZoneOffset; /** * API 통합 테스트 베이스 클래스 diff --git a/src/test/java/zim/tave/memory/controller/CountryControllerTest.java b/src/test/java/zim/tave/memory/controller/CountryControllerTest.java index a7f1f1f..d23872c 100644 --- a/src/test/java/zim/tave/memory/controller/CountryControllerTest.java +++ b/src/test/java/zim/tave/memory/controller/CountryControllerTest.java @@ -41,7 +41,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; diff --git a/src/test/java/zim/tave/memory/controller/JoinControllerTest.java b/src/test/java/zim/tave/memory/controller/JoinControllerTest.java index f6e9fbc..325e765 100644 --- a/src/test/java/zim/tave/memory/controller/JoinControllerTest.java +++ b/src/test/java/zim/tave/memory/controller/JoinControllerTest.java @@ -23,7 +23,6 @@ import java.time.LocalDate; -import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; diff --git a/src/test/java/zim/tave/memory/controller/TimelineControllerTest.java b/src/test/java/zim/tave/memory/controller/TimelineControllerTest.java index a3057da..10000a2 100644 --- a/src/test/java/zim/tave/memory/controller/TimelineControllerTest.java +++ b/src/test/java/zim/tave/memory/controller/TimelineControllerTest.java @@ -38,7 +38,6 @@ import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; diff --git a/src/test/java/zim/tave/memory/integration/ImageUploadIntegrationTest.java b/src/test/java/zim/tave/memory/integration/ImageUploadIntegrationTest.java index 7b6402c..5cd70ba 100644 --- a/src/test/java/zim/tave/memory/integration/ImageUploadIntegrationTest.java +++ b/src/test/java/zim/tave/memory/integration/ImageUploadIntegrationTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.assertj.core.api.Assertions.*; diff --git a/src/test/java/zim/tave/memory/integration/IntegrationTestBase.java b/src/test/java/zim/tave/memory/integration/IntegrationTestBase.java index 6b8ee9e..215ac2f 100644 --- a/src/test/java/zim/tave/memory/integration/IntegrationTestBase.java +++ b/src/test/java/zim/tave/memory/integration/IntegrationTestBase.java @@ -8,7 +8,6 @@ import java.time.LocalDate; import java.time.OffsetDateTime; -import java.time.ZoneOffset; /** * 통합 테스트 베이스 클래스 diff --git a/src/test/java/zim/tave/memory/integration/MyPageIntegrationTest.java b/src/test/java/zim/tave/memory/integration/MyPageIntegrationTest.java index 2a42958..efa4504 100644 --- a/src/test/java/zim/tave/memory/integration/MyPageIntegrationTest.java +++ b/src/test/java/zim/tave/memory/integration/MyPageIntegrationTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import zim.tave.memory.domain.*; -import zim.tave.memory.dto.request.CreateDiaryRequest; import zim.tave.memory.dto.response.MyPageResponseDto; import zim.tave.memory.dto.response.VisitedCountryListResponseDto; import zim.tave.memory.service.MyPageService; @@ -14,7 +13,6 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.ZoneOffset; -import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java b/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java index 6c3a47e..6a57bb2 100644 --- a/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java +++ b/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java @@ -1,7 +1,6 @@ package zim.tave.memory.service; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/zim/tave/memory/service/JoinServiceTest.java b/src/test/java/zim/tave/memory/service/JoinServiceTest.java index 86ca849..c184842 100644 --- a/src/test/java/zim/tave/memory/service/JoinServiceTest.java +++ b/src/test/java/zim/tave/memory/service/JoinServiceTest.java @@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import static zim.tave.memory.service.SettingServiceTest.*; @ExtendWith(MockitoExtension.class) public class JoinServiceTest { diff --git a/src/test/java/zim/tave/memory/service/MyPageServiceTest.java b/src/test/java/zim/tave/memory/service/MyPageServiceTest.java index b3e577a..6a1a43d 100644 --- a/src/test/java/zim/tave/memory/service/MyPageServiceTest.java +++ b/src/test/java/zim/tave/memory/service/MyPageServiceTest.java @@ -5,7 +5,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; import zim.tave.memory.domain.Country; import zim.tave.memory.domain.User; import zim.tave.memory.domain.VisitedCountry; From 8a520bf11a767c7fa6f181b21f457c4582c7fd93 Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Mon, 2 Feb 2026 15:53:20 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refactor=20:=20camelCase=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zim/tave/memory/domain/AlarmHistory.java | 12 ++++++------ .../java/zim/tave/memory/domain/Board.java | 6 ++++-- .../tave/memory/domain/BoardStickerMap.java | 7 ++++--- .../zim/tave/memory/domain/BoardTheme.java | 7 ++++--- .../java/zim/tave/memory/domain/Country.java | 2 ++ .../java/zim/tave/memory/domain/Diary.java | 11 ++++++----- .../zim/tave/memory/domain/DiaryImage.java | 6 ++++-- .../java/zim/tave/memory/domain/Emotion.java | 2 ++ .../java/zim/tave/memory/domain/Sticker.java | 5 +++-- .../java/zim/tave/memory/domain/Trip.java | 12 ++++++++---- .../zim/tave/memory/domain/TripTheme.java | 3 +++ .../java/zim/tave/memory/domain/User.java | 19 +++++++++++++------ .../tave/memory/domain/VisitedCountry.java | 3 +++ .../java/zim/tave/memory/domain/Weather.java | 2 ++ src/main/resources/application.yml | 2 +- .../db/migration-dev/V4__test_user.sql | 6 +++--- src/main/resources/db/migration/V1__init.sql | 8 ++++---- 17 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/main/java/zim/tave/memory/domain/AlarmHistory.java b/src/main/java/zim/tave/memory/domain/AlarmHistory.java index 3805259..4570611 100644 --- a/src/main/java/zim/tave/memory/domain/AlarmHistory.java +++ b/src/main/java/zim/tave/memory/domain/AlarmHistory.java @@ -14,28 +14,28 @@ public class AlarmHistory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "alarm_history_id") + @Column(name = "alarmHistoryId") private Long id; // 알림을 받을 사용자 - @Column(nullable = false) + @Column(name = "userId", nullable = false) private Long userId; // 어떤 여행에 대한 알림인지 - @Column(nullable = false) + @Column(name = "tripId", nullable = false) private Long tripId; // 알림 종류 @Enumerated(EnumType.STRING) - @Column(nullable = false, length = 50) + @Column(name = "alarmType", nullable = false, length = 50) private AlarmType alarmType; // 실제 발송 시각 - @Column(nullable = false) + @Column(name = "sentAt", nullable = false) private LocalDateTime sentAt; // 일 단위 조회 최적화를 위한 필드 (캡 계산용) - @Column(nullable = false) + @Column(name = "sentDate", nullable = false) private LocalDate sentDate; public static AlarmHistory create( diff --git a/src/main/java/zim/tave/memory/domain/Board.java b/src/main/java/zim/tave/memory/domain/Board.java index bb69fbc..5dfcded 100644 --- a/src/main/java/zim/tave/memory/domain/Board.java +++ b/src/main/java/zim/tave/memory/domain/Board.java @@ -18,14 +18,16 @@ public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "boardId") private Long boardId; - @Column(nullable = false, length = 100) + @Column(name = "title", nullable = false, length = 100) private String title; - @Column(nullable = false, updatable = false) + @Column(name = "createdAt", nullable = false, updatable = false) private OffsetDateTime createdAt; + @Column(name = "updatedAt") private OffsetDateTime updatedAt; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/zim/tave/memory/domain/BoardStickerMap.java b/src/main/java/zim/tave/memory/domain/BoardStickerMap.java index 51f2e12..29034df 100644 --- a/src/main/java/zim/tave/memory/domain/BoardStickerMap.java +++ b/src/main/java/zim/tave/memory/domain/BoardStickerMap.java @@ -14,6 +14,7 @@ public class BoardStickerMap { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "boardStickerId") private Long boardStickerId; @ManyToOne(fetch = FetchType.LAZY) @@ -24,12 +25,12 @@ public class BoardStickerMap { @JoinColumn(name = "stickerId", nullable = false) private Sticker sticker; - @Column(nullable = false) + @Column(name = "posX", nullable = false) private BigDecimal posX; - @Column(nullable = false) + @Column(name = "posY", nullable = false) private BigDecimal posY; - @Column(nullable = false) + @Column(name = "rotation", nullable = false) private BigDecimal rotation; } diff --git a/src/main/java/zim/tave/memory/domain/BoardTheme.java b/src/main/java/zim/tave/memory/domain/BoardTheme.java index b9b5649..3c5c62c 100644 --- a/src/main/java/zim/tave/memory/domain/BoardTheme.java +++ b/src/main/java/zim/tave/memory/domain/BoardTheme.java @@ -13,14 +13,15 @@ public class BoardTheme { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "boardThemeId") private Long boardThemeId; - @Column(nullable = false, length = 100) + @Column(name = "themeName", nullable = false, length = 100) private String themeName; - @Column(nullable = false, length = 255) + @Column(name = "thumbnailUrl", nullable = false, length = 255) private String thumbnailUrl; - @Column(nullable = false, length = 255) + @Column(name = "cardUrl", nullable = false, length = 255) private String cardUrl; } diff --git a/src/main/java/zim/tave/memory/domain/Country.java b/src/main/java/zim/tave/memory/domain/Country.java index ed1bd9f..f4289cd 100644 --- a/src/main/java/zim/tave/memory/domain/Country.java +++ b/src/main/java/zim/tave/memory/domain/Country.java @@ -14,8 +14,10 @@ public class Country { @Id + @Column(name = "countryCode") private String countryCode; // 예: "KR", "US" + @Column(name = "countryName") private String countryName; @Column(columnDefinition = "VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") diff --git a/src/main/java/zim/tave/memory/domain/Diary.java b/src/main/java/zim/tave/memory/domain/Diary.java index 3283d15..95b742b 100644 --- a/src/main/java/zim/tave/memory/domain/Diary.java +++ b/src/main/java/zim/tave/memory/domain/Diary.java @@ -30,16 +30,17 @@ public class Diary { @JoinColumn(name = "countryId") private Country country; - @Column(nullable = false) + @Column(name = "city", nullable = false) private String city; - @Column(nullable = false) + @Column(name = "dateTime", nullable = false) private OffsetDateTime dateTime; @Lob - @Column(nullable = false, length = 88) + @Column(name = "content", nullable = false, length = 88) private String content; + @Column(name = "detailedLocation") private String detailedLocation; @ManyToOne(fetch = FetchType.LAZY) @@ -54,10 +55,10 @@ public class Diary { @BatchSize(size = 20) // lazy loading 시 20개씩 조회 private List diaryImages = new ArrayList<>(); - @Column(updatable = false) + @Column(name = "createdAt", updatable = false) private OffsetDateTime createdAt; - @Column(nullable = false) + @Column(name = "isStored", nullable = false) private Boolean isStored; @PrePersist diff --git a/src/main/java/zim/tave/memory/domain/DiaryImage.java b/src/main/java/zim/tave/memory/domain/DiaryImage.java index ba4a53b..f993e64 100644 --- a/src/main/java/zim/tave/memory/domain/DiaryImage.java +++ b/src/main/java/zim/tave/memory/domain/DiaryImage.java @@ -18,15 +18,17 @@ public class DiaryImage { @JoinColumn(name = "diaryId") private Diary diary; - @Column(nullable = false) + @Column(name = "imageUrl", nullable = false) private String imageUrl; @Enumerated(EnumType.STRING) - @Column(nullable = false) + @Column(name = "cameraType", nullable = false) private CameraType cameraType; // FRONT, BACK + @Column(name = "isRepresentative") private boolean isRepresentative; // 사용자가 선택한 대표사진 + @Column(name = "imageOrder") private int imageOrder; // 이미지 순서 (1, 2) public enum CameraType { diff --git a/src/main/java/zim/tave/memory/domain/Emotion.java b/src/main/java/zim/tave/memory/domain/Emotion.java index 03a0026..b12103a 100644 --- a/src/main/java/zim/tave/memory/domain/Emotion.java +++ b/src/main/java/zim/tave/memory/domain/Emotion.java @@ -14,7 +14,9 @@ public class Emotion { @Column(name = "emotion_id") private Long id; + @Column(name = "name") private String name; + @Column(name = "colorCode") private String colorCode; public Emotion(String name, String colorCode) { diff --git a/src/main/java/zim/tave/memory/domain/Sticker.java b/src/main/java/zim/tave/memory/domain/Sticker.java index 521b697..16cf907 100644 --- a/src/main/java/zim/tave/memory/domain/Sticker.java +++ b/src/main/java/zim/tave/memory/domain/Sticker.java @@ -13,11 +13,12 @@ public class Sticker { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "stickerId") private Long stickerId; - @Column(nullable = false) + @Column(name = "name", nullable = false) private String name; - @Column(nullable = false) + @Column(name = "imageUrl", nullable = false) private String imageUrl; } diff --git a/src/main/java/zim/tave/memory/domain/Trip.java b/src/main/java/zim/tave/memory/domain/Trip.java index 5d20091..df4e973 100644 --- a/src/main/java/zim/tave/memory/domain/Trip.java +++ b/src/main/java/zim/tave/memory/domain/Trip.java @@ -19,20 +19,22 @@ public class Trip { @Column(name = "tripId") private Long id; - @Column(length = 14, nullable = false) + @Column(name = "tripName", length = 14, nullable = false) private String tripName; - @Column(length = 56) + @Column(name = "description", length = 56) private String description; - @Column(nullable = false) + @Column(name = "startDate", nullable = false) private LocalDate startDate; - @Column(nullable = false) + @Column(name = "endDate", nullable = false) private LocalDate endDate; + @Column(name = "isStored") private Boolean isStored = false; //보관 여부 확인 + @Column(name = "isPast") private Boolean isPast = false; //과거 여행 여부 확인 @JsonBackReference @@ -45,8 +47,10 @@ public class Trip { private TripTheme tripTheme; @Lob + @Column(name = "content") private String content; + @Column(name = "representativeImageUrl") private String representativeImageUrl; @JsonManagedReference diff --git a/src/main/java/zim/tave/memory/domain/TripTheme.java b/src/main/java/zim/tave/memory/domain/TripTheme.java index 2aebd40..9ee2417 100644 --- a/src/main/java/zim/tave/memory/domain/TripTheme.java +++ b/src/main/java/zim/tave/memory/domain/TripTheme.java @@ -17,10 +17,13 @@ public class TripTheme { @Id @GeneratedValue private Long id; + @Column(name = "themeName") private String themeName; + @Column(name = "sampleImageUrl") private String sampleImageUrl; // 사용자가 테마를 선택할 때 보여줄 샘플 이미지 + @Column(name = "cardImageUrl") private String cardImageUrl; // 실제 카드에 들어갈 이미지 @JsonManagedReference diff --git a/src/main/java/zim/tave/memory/domain/User.java b/src/main/java/zim/tave/memory/domain/User.java index cdaaf98..2fc6c18 100644 --- a/src/main/java/zim/tave/memory/domain/User.java +++ b/src/main/java/zim/tave/memory/domain/User.java @@ -19,31 +19,38 @@ public class User { private Long id; //카카오 로그인 - @Column(unique=true) + @Column(name = "kakaoId", unique=true) private String kakaoId; + @Column(name = "profileImageUrl") private String profileImageUrl; - @Column(name = "sur_name") + @Column(name = "surName") private String surName; - @Column(name = "first_name") + @Column(name = "firstName") private String firstName; - @Column(name = "korean_name") + @Column(name = "koreanName") private String koreanName; + @Column(name = "createdAt") private LocalDate createdAt; + @Column(name = "status") private boolean status; + @Column(name = "birth") private LocalDate birth; + @Column(name = "nationality") private String nationality; - @Column(nullable = false) + @Column(name = "isRegistered", nullable = false) private boolean isRegistered; // 회원가입 완료 여 (true = 가입 완료) //마이페이지 Statistics 정보 + @Column(name = "diaryCount") private Long diaryCount; //일기 수 + @Column(name = "visitedCountryCount") private Long visitedCountryCount; //방문한 나라 수 - @Column(length = 255) + @Column(name = "flags", length = 255) private String flags; //국기 @JsonManagedReference diff --git a/src/main/java/zim/tave/memory/domain/VisitedCountry.java b/src/main/java/zim/tave/memory/domain/VisitedCountry.java index 008e212..aefd38d 100644 --- a/src/main/java/zim/tave/memory/domain/VisitedCountry.java +++ b/src/main/java/zim/tave/memory/domain/VisitedCountry.java @@ -16,10 +16,13 @@ public class VisitedCountry { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "visitedCountryId") private Long visitedCountryId; + @Column(name = "color") private String color; + @Column(name = "createdAt") private OffsetDateTime createdAt; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/zim/tave/memory/domain/Weather.java b/src/main/java/zim/tave/memory/domain/Weather.java index 906affb..810e976 100644 --- a/src/main/java/zim/tave/memory/domain/Weather.java +++ b/src/main/java/zim/tave/memory/domain/Weather.java @@ -12,6 +12,8 @@ public class Weather { @Column(name = "weather_id") private Long id; + @Column(name = "name") private String name; + @Column(name = "iconUrl") private String iconUrl; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 90ca020..353620e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,7 +6,7 @@ spring: import: optional:application-secret.yml datasource: - url: jdbc:mysql://localhost:3306/memory_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/memory_db_test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: memory_user driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/db/migration-dev/V4__test_user.sql b/src/main/resources/db/migration-dev/V4__test_user.sql index fbdf614..606755a 100644 --- a/src/main/resources/db/migration-dev/V4__test_user.sql +++ b/src/main/resources/db/migration-dev/V4__test_user.sql @@ -9,9 +9,9 @@ INSERT IGNORE INTO `user` ( `kakaoId`, `profileImageUrl`, - `sur_name`, - `first_name`, - `korean_name`, + `surName`, + `firstName`, + `koreanName`, `birth`, `nationality`, `createdAt`, diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql index 9c8276f..9f9b8be 100644 --- a/src/main/resources/db/migration/V1__init.sql +++ b/src/main/resources/db/migration/V1__init.sql @@ -52,9 +52,9 @@ CREATE TABLE IF NOT EXISTS `user` ( `userId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `kakaoId` VARCHAR(255) UNIQUE, `profileImageUrl` VARCHAR(255), - `sur_name` VARCHAR(255), - `first_name` VARCHAR(255), - `korean_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `surName` VARCHAR(255), + `firstName` VARCHAR(255), + `koreanName` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `createdAt` DATE DEFAULT (CURRENT_DATE), `status` BOOLEAN DEFAULT FALSE, `birth` DATE, @@ -160,7 +160,7 @@ CREATE TABLE IF NOT EXISTS `board_sticker_map` ( -- AlarmHistory 테이블 (FK 없음, 하지만 User, Trip 참조) CREATE TABLE IF NOT EXISTS `alarm_history` ( - `alarm_history_id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + `alarmHistoryId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `userId` BIGINT NOT NULL, `tripId` BIGINT NOT NULL, `alarmType` VARCHAR(50) NOT NULL, From 95e3b7bce8852940380fb0ce238013a681c0ef2f Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Tue, 3 Feb 2026 13:51:31 +0900 Subject: [PATCH 07/12] =?UTF-8?q?refactor(domain):=20=20GenerationType.IDE?= =?UTF-8?q?NTITY=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20&=20Removed=20unnecess?= =?UTF-8?q?ary=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/zim/tave/memory/domain/Country.java | 4 +--- src/main/java/zim/tave/memory/domain/Diary.java | 3 ++- src/main/java/zim/tave/memory/domain/DiaryImage.java | 3 ++- src/main/java/zim/tave/memory/domain/Emotion.java | 3 ++- src/main/java/zim/tave/memory/domain/Trip.java | 3 ++- src/main/java/zim/tave/memory/domain/TripTheme.java | 3 ++- src/main/java/zim/tave/memory/domain/Weather.java | 3 ++- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/zim/tave/memory/domain/Country.java b/src/main/java/zim/tave/memory/domain/Country.java index f4289cd..1928dc1 100644 --- a/src/main/java/zim/tave/memory/domain/Country.java +++ b/src/main/java/zim/tave/memory/domain/Country.java @@ -1,8 +1,6 @@ package zim.tave.memory.domain; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Column; +import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/src/main/java/zim/tave/memory/domain/Diary.java b/src/main/java/zim/tave/memory/domain/Diary.java index 95b742b..9f025c2 100644 --- a/src/main/java/zim/tave/memory/domain/Diary.java +++ b/src/main/java/zim/tave/memory/domain/Diary.java @@ -14,7 +14,8 @@ @Getter @Setter public class Diary { - @Id @GeneratedValue + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "diaryId") private Long id; diff --git a/src/main/java/zim/tave/memory/domain/DiaryImage.java b/src/main/java/zim/tave/memory/domain/DiaryImage.java index f993e64..6628d3a 100644 --- a/src/main/java/zim/tave/memory/domain/DiaryImage.java +++ b/src/main/java/zim/tave/memory/domain/DiaryImage.java @@ -6,11 +6,12 @@ import com.fasterxml.jackson.annotation.JsonCreator; @Entity +@Table(name = "DiaryImage") @Getter @Setter public class DiaryImage { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "DiaryImageId") private Long id; diff --git a/src/main/java/zim/tave/memory/domain/Emotion.java b/src/main/java/zim/tave/memory/domain/Emotion.java index b12103a..fd8a18e 100644 --- a/src/main/java/zim/tave/memory/domain/Emotion.java +++ b/src/main/java/zim/tave/memory/domain/Emotion.java @@ -10,7 +10,8 @@ @Getter @Setter public class Emotion { - @Id @GeneratedValue + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "emotion_id") private Long id; diff --git a/src/main/java/zim/tave/memory/domain/Trip.java b/src/main/java/zim/tave/memory/domain/Trip.java index df4e973..46bc75e 100644 --- a/src/main/java/zim/tave/memory/domain/Trip.java +++ b/src/main/java/zim/tave/memory/domain/Trip.java @@ -15,7 +15,8 @@ @Getter @Setter public class Trip { - @Id @GeneratedValue + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "tripId") private Long id; diff --git a/src/main/java/zim/tave/memory/domain/TripTheme.java b/src/main/java/zim/tave/memory/domain/TripTheme.java index 9ee2417..3f6d6fa 100644 --- a/src/main/java/zim/tave/memory/domain/TripTheme.java +++ b/src/main/java/zim/tave/memory/domain/TripTheme.java @@ -14,7 +14,8 @@ @NoArgsConstructor public class TripTheme { - @Id @GeneratedValue + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "themeName") diff --git a/src/main/java/zim/tave/memory/domain/Weather.java b/src/main/java/zim/tave/memory/domain/Weather.java index 810e976..efa8e5f 100644 --- a/src/main/java/zim/tave/memory/domain/Weather.java +++ b/src/main/java/zim/tave/memory/domain/Weather.java @@ -8,7 +8,8 @@ @Getter @Setter public class Weather { - @Id @GeneratedValue + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "weather_id") private Long id; From b90122d46291e7bd834e67884add9b6d3980a83f Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Tue, 3 Feb 2026 13:52:42 +0900 Subject: [PATCH 08/12] =?UTF-8?q?refactor(sql)=20:=20table=20=EB=AA=85=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 1 + .../db/migration-dev/V4__test_user.sql | 8 +-- src/main/resources/db/migration/V1__init.sql | 66 +++++++++---------- .../db/migration/V2__insert_basic_data.sql | 10 +-- .../db/migration/V3__insert_countries.sql | 2 +- 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 353620e..9b8b152 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -28,6 +28,7 @@ spring: properties: hibernate: format_sql: true + physical_naming_strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl jdbc: time_zone: UTC timezone: diff --git a/src/main/resources/db/migration-dev/V4__test_user.sql b/src/main/resources/db/migration-dev/V4__test_user.sql index 606755a..24fe2b3 100644 --- a/src/main/resources/db/migration-dev/V4__test_user.sql +++ b/src/main/resources/db/migration-dev/V4__test_user.sql @@ -6,7 +6,7 @@ -- ============================================ -- 테스트 사용자 삽입 (이미 존재하는 경우 무시) -INSERT IGNORE INTO `user` ( +INSERT IGNORE INTO `User` ( `kakaoId`, `profileImageUrl`, `surName`, @@ -37,10 +37,10 @@ INSERT IGNORE INTO `user` ( ); -- 테스트 사용자의 Setting 생성 (이미 존재하는 경우 무시) -INSERT IGNORE INTO `setting` (`userId`, `alarm`) +INSERT IGNORE INTO `Setting` (`userId`, `alarm`) SELECT u.`userId`, TRUE -FROM `user` u +FROM `User` u WHERE u.`kakaoId` = 'test_강지혜' AND NOT EXISTS ( - SELECT 1 FROM `setting` s WHERE s.`userId` = u.`userId` + SELECT 1 FROM `Setting` s WHERE s.`userId` = u.`userId` ); diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql index 9f9b8be..a08b8e3 100644 --- a/src/main/resources/db/migration/V1__init.sql +++ b/src/main/resources/db/migration/V1__init.sql @@ -4,28 +4,28 @@ -- ============================================ -- Country 테이블 (FK 없음, 가장 먼저 생성) -CREATE TABLE IF NOT EXISTS `country` ( +CREATE TABLE IF NOT EXISTS `Country` ( `countryCode` VARCHAR(2) NOT NULL PRIMARY KEY, `countryName` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `emoji` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Emotion 테이블 (FK 없음) -CREATE TABLE IF NOT EXISTS `emotion` ( +CREATE TABLE IF NOT EXISTS `Emotion` ( `emotion_id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `colorCode` VARCHAR(255) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Weather 테이블 (FK 없음) -CREATE TABLE IF NOT EXISTS `weather` ( +CREATE TABLE IF NOT EXISTS `Weather` ( `weather_id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `iconUrl` VARCHAR(255) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- TripTheme 테이블 (FK 없음) -CREATE TABLE IF NOT EXISTS `trip_theme` ( +CREATE TABLE IF NOT EXISTS `TripTheme` ( `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `themeName` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `sampleImageUrl` VARCHAR(255), @@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS `trip_theme` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- BoardTheme 테이블 (FK 없음) -CREATE TABLE IF NOT EXISTS `board_theme` ( +CREATE TABLE IF NOT EXISTS `BoardTheme` ( `boardThemeId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `themeName` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `thumbnailUrl` VARCHAR(255) NOT NULL, @@ -41,14 +41,14 @@ CREATE TABLE IF NOT EXISTS `board_theme` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Sticker 테이블 (FK 없음) -CREATE TABLE IF NOT EXISTS `sticker` ( +CREATE TABLE IF NOT EXISTS `Sticker` ( `stickerId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(255) NOT NULL, `imageUrl` VARCHAR(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- User 테이블 (FK 없음) -CREATE TABLE IF NOT EXISTS `user` ( +CREATE TABLE IF NOT EXISTS `User` ( `userId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `kakaoId` VARCHAR(255) UNIQUE, `profileImageUrl` VARCHAR(255), @@ -66,14 +66,14 @@ CREATE TABLE IF NOT EXISTS `user` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Setting 테이블 (FK: User) -CREATE TABLE IF NOT EXISTS `setting` ( +CREATE TABLE IF NOT EXISTS `Setting` ( `userId` BIGINT NOT NULL PRIMARY KEY, `alarm` BOOLEAN NOT NULL DEFAULT TRUE, - CONSTRAINT `fk_setting_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE + CONSTRAINT `fk_setting_user` FOREIGN KEY (`userId`) REFERENCES `User` (`userId`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Trip 테이블 (FK: User, TripTheme) -CREATE TABLE IF NOT EXISTS `trip` ( +CREATE TABLE IF NOT EXISTS `Trip` ( `tripId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `tripName` VARCHAR(14) NOT NULL, `description` VARCHAR(56), @@ -83,83 +83,83 @@ CREATE TABLE IF NOT EXISTS `trip` ( `isPast` BOOLEAN DEFAULT FALSE, `userId` BIGINT, `tripThemeId` BIGINT, - `content` LONGTEXT, + `content` TINYTEXT, `representativeImageUrl` VARCHAR(255), - CONSTRAINT `fk_trip_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, - CONSTRAINT `fk_trip_trip_theme` FOREIGN KEY (`tripThemeId`) REFERENCES `trip_theme` (`id`) + CONSTRAINT `fk_trip_user` FOREIGN KEY (`userId`) REFERENCES `User` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_trip_trip_theme` FOREIGN KEY (`tripThemeId`) REFERENCES `TripTheme` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Diary 테이블 (FK: User, Trip, Country, Emotion, Weather) -CREATE TABLE IF NOT EXISTS `diary` ( +CREATE TABLE IF NOT EXISTS `Diary` ( `diaryId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `userId` BIGINT, `tripId` BIGINT, `countryId` VARCHAR(2), `city` VARCHAR(255) NOT NULL, `dateTime` DATETIME(6) NOT NULL, - `content` LONGTEXT NOT NULL, + `content` TINYTEXT NOT NULL, `detailedLocation` VARCHAR(255), `emotionId` BIGINT, `weatherId` BIGINT, `createdAt` DATETIME(6) DEFAULT (UTC_TIMESTAMP()), `isStored` BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT `fk_diary_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, - CONSTRAINT `fk_diary_trip` FOREIGN KEY (`tripId`) REFERENCES `trip` (`tripId`) ON DELETE CASCADE, - CONSTRAINT `fk_diary_country` FOREIGN KEY (`countryId`) REFERENCES `country` (`countryCode`), - CONSTRAINT `fk_diary_emotion` FOREIGN KEY (`emotionId`) REFERENCES `emotion` (`emotion_id`), - CONSTRAINT `fk_diary_weather` FOREIGN KEY (`weatherId`) REFERENCES `weather` (`weather_id`) + CONSTRAINT `fk_diary_user` FOREIGN KEY (`userId`) REFERENCES `User` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_diary_trip` FOREIGN KEY (`tripId`) REFERENCES `Trip` (`tripId`) ON DELETE CASCADE, + CONSTRAINT `fk_diary_country` FOREIGN KEY (`countryId`) REFERENCES `Country` (`countryCode`), + CONSTRAINT `fk_diary_emotion` FOREIGN KEY (`emotionId`) REFERENCES `Emotion` (`emotion_id`), + CONSTRAINT `fk_diary_weather` FOREIGN KEY (`weatherId`) REFERENCES `Weather` (`weather_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- DiaryImage 테이블 (FK: Diary) -CREATE TABLE IF NOT EXISTS `diary_image` ( +CREATE TABLE IF NOT EXISTS `DiaryImage` ( `DiaryImageId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `diaryId` BIGINT, `imageUrl` VARCHAR(255) NOT NULL, `cameraType` VARCHAR(255) NOT NULL, `isRepresentative` BOOLEAN DEFAULT FALSE, `imageOrder` INT, - CONSTRAINT `fk_diary_image_diary` FOREIGN KEY (`diaryId`) REFERENCES `diary` (`diaryId`) ON DELETE CASCADE + CONSTRAINT `fk_diary_image_diary` FOREIGN KEY (`diaryId`) REFERENCES `Diary` (`diaryId`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- VisitedCountry 테이블 (FK: User, Country, Emotion) -CREATE TABLE IF NOT EXISTS `visited_country` ( +CREATE TABLE IF NOT EXISTS `VisitedCountry` ( `visitedCountryId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `color` VARCHAR(255), `createdAt` DATETIME(6) DEFAULT (UTC_TIMESTAMP()), `userId` BIGINT, `countryCode` VARCHAR(2), `emotionId` BIGINT, - CONSTRAINT `fk_visited_country_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, - CONSTRAINT `fk_visited_country_country` FOREIGN KEY (`countryCode`) REFERENCES `country` (`countryCode`), - CONSTRAINT `fk_visited_country_emotion` FOREIGN KEY (`emotionId`) REFERENCES `emotion` (`emotion_id`) + CONSTRAINT `fk_visited_country_user` FOREIGN KEY (`userId`) REFERENCES `User` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_visited_country_country` FOREIGN KEY (`countryCode`) REFERENCES `Country` (`countryCode`), + CONSTRAINT `fk_visited_country_emotion` FOREIGN KEY (`emotionId`) REFERENCES `Emotion` (`emotion_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Board 테이블 (FK: User, BoardTheme) -CREATE TABLE IF NOT EXISTS `board` ( +CREATE TABLE IF NOT EXISTS `Board` ( `boardId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `title` VARCHAR(100) NOT NULL, `createdAt` DATETIME(6) NOT NULL DEFAULT (UTC_TIMESTAMP()), `updatedAt` DATETIME(6), `userId` BIGINT NOT NULL, `boardThemeId` BIGINT NOT NULL, - CONSTRAINT `fk_board_user` FOREIGN KEY (`userId`) REFERENCES `user` (`userId`) ON DELETE CASCADE, - CONSTRAINT `fk_board_board_theme` FOREIGN KEY (`boardThemeId`) REFERENCES `board_theme` (`boardThemeId`) + CONSTRAINT `fk_board_user` FOREIGN KEY (`userId`) REFERENCES `User` (`userId`) ON DELETE CASCADE, + CONSTRAINT `fk_board_board_theme` FOREIGN KEY (`boardThemeId`) REFERENCES `BoardTheme` (`boardThemeId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- BoardStickerMap 테이블 (FK: Board, Sticker) -CREATE TABLE IF NOT EXISTS `board_sticker_map` ( +CREATE TABLE IF NOT EXISTS `BoardStickerMap` ( `boardStickerId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `boardId` BIGINT NOT NULL, `stickerId` BIGINT NOT NULL, `posX` DECIMAL(19,2) NOT NULL, `posY` DECIMAL(19,2) NOT NULL, `rotation` DECIMAL(19,2) NOT NULL, - CONSTRAINT `fk_board_sticker_map_board` FOREIGN KEY (`boardId`) REFERENCES `board` (`boardId`) ON DELETE CASCADE, - CONSTRAINT `fk_board_sticker_map_sticker` FOREIGN KEY (`stickerId`) REFERENCES `sticker` (`stickerId`) + CONSTRAINT `fk_board_sticker_map_board` FOREIGN KEY (`boardId`) REFERENCES `Board` (`boardId`) ON DELETE CASCADE, + CONSTRAINT `fk_board_sticker_map_sticker` FOREIGN KEY (`stickerId`) REFERENCES `Sticker` (`stickerId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- AlarmHistory 테이블 (FK 없음, 하지만 User, Trip 참조) -CREATE TABLE IF NOT EXISTS `alarm_history` ( +CREATE TABLE IF NOT EXISTS `AlarmHistory` ( `alarmHistoryId` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `userId` BIGINT NOT NULL, `tripId` BIGINT NOT NULL, diff --git a/src/main/resources/db/migration/V2__insert_basic_data.sql b/src/main/resources/db/migration/V2__insert_basic_data.sql index 4fc7f72..96c2ea0 100644 --- a/src/main/resources/db/migration/V2__insert_basic_data.sql +++ b/src/main/resources/db/migration/V2__insert_basic_data.sql @@ -4,7 +4,7 @@ -- ============================================ -- TripTheme 데이터 삽입 -INSERT INTO `trip_theme` (`themeName`, `sampleImageUrl`, `cardImageUrl`) VALUES +INSERT INTO `TripTheme` (`themeName`, `sampleImageUrl`, `cardImageUrl`) VALUES ('기본', 'https://me-mory01.mooo.com/api/files?key=images/d69be93f-fef6-4a73-815f-ea6b93fd2d59_trip_thumb_default.png', 'https://me-mory01.mooo.com/api/files?key=images/6e56681d-9fc3-43d2-a803-0ef165c63c3c_trip_card_default.png'), ('Grey', 'https://me-mory01.mooo.com/api/files?key=images/f0d5e1f7-2718-4dac-a780-d6f6e5f2d42a_trip_thumb_grey.png', 'https://me-mory01.mooo.com/api/files?key=images/462d91ad-b618-4449-805f-35f07187247b_trip_card_grey.png'), ('탑승권', 'https://me-mory01.mooo.com/api/files?key=images/cbcf48b8-8469-4e0f-b597-7ef27cc6190e_trip_thumb_boardingpass.png', 'https://me-mory01.mooo.com/api/files?key=images/3e637461-f9a2-477d-ae06-4245033174a9_trip_card_boardingpass.png'), @@ -13,7 +13,7 @@ INSERT INTO `trip_theme` (`themeName`, `sampleImageUrl`, `cardImageUrl`) VALUES ('Forest', 'https://me-mory01.mooo.com/api/files?key=images/6f40c3c9-313f-44e5-9624-3264991b2f9b_trip_thumb_forest.png', 'https://me-mory01.mooo.com/api/files?key=images/99589e53-9539-46eb-b702-644fab7a87e3_trip_card_forest.png'); -- Emotion 데이터 삽입 -INSERT INTO `emotion` (`name`, `colorCode`) VALUES +INSERT INTO `Emotion` (`name`, `colorCode`) VALUES ('기본', '#EEEEEE'), ('설렘', '#FDD7DE'), ('신기함', '#FFCB6B'), @@ -29,7 +29,7 @@ INSERT INTO `emotion` (`name`, `colorCode`) VALUES ('벅참', '#800020'); -- Weather 데이터 삽입 -INSERT INTO `weather` (`name`, `iconUrl`) VALUES +INSERT INTO `Weather` (`name`, `iconUrl`) VALUES ('맑음', 'https://me-mory01.mooo.com/api/files?key=images/7de47dfb-6f09-47d2-9268-2b8c49c2f9bd_weather_sunny.png'), ('구름', 'https://me-mory01.mooo.com/api/files?key=images/6a8c4a38-0ccd-479c-ba70-7202d7535bf7_weather_cloudy.png'), ('비', 'https://me-mory01.mooo.com/api/files?key=images/72a8e191-9b31-4646-ae48-cb31fb543de7_weather_rainy.png'), @@ -37,12 +37,12 @@ INSERT INTO `weather` (`name`, `iconUrl`) VALUES ('눈', 'https://me-mory01.mooo.com/api/files?key=images/3b298957-360e-4ea4-86ea-bcd65fcd3f77_weather_snowy.png'); -- BoardTheme 데이터 삽입 -INSERT INTO `board_theme` (`themeName`, `thumbnailUrl`, `cardUrl`) VALUES +INSERT INTO `BoardTheme` (`themeName`, `thumbnailUrl`, `cardUrl`) VALUES ('칠판', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/chalkboard_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/73212582-a940-4929-9c9b-f99ce68f3238_board_card_chalkboard.png'), ('식탁보', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/tablecloth_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/ea8230ad-a70a-4f87-acb4-648add56b1b8_board_card_tablecloth.png'), ('나무보드', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/board_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/ecee4553-72cc-4097-8d20-10a7ee889269_board_card_woodboard.png'), ('체스판', 'https://me-mory01.mooo.com/api/files?key=images/board_theme/chess_thumb.png', 'https://me-mory01.mooo.com/api/files?key=images/e026afe7-1619-4ee5-b3e7-c2564d88447c_board_card_chess.png'); -- Sticker 데이터 삽입 -INSERT INTO `sticker` (`name`, `imageUrl`) VALUES +INSERT INTO `Sticker` (`name`, `imageUrl`) VALUES ('Star', 'https://me-mory01.mooo.com/api/files?key=images/6d4c8b2f-00ec-41ae-bd57-9ce914e1c78d_sticker_star.png'); diff --git a/src/main/resources/db/migration/V3__insert_countries.sql b/src/main/resources/db/migration/V3__insert_countries.sql index c83ad84..991f14c 100644 --- a/src/main/resources/db/migration/V3__insert_countries.sql +++ b/src/main/resources/db/migration/V3__insert_countries.sql @@ -3,7 +3,7 @@ -- Country 데이터 삽입 (245개 국가) -- ============================================ -INSERT INTO `country` (`countryCode`, `countryName`, `emoji`) VALUES +INSERT INTO `Country` (`countryCode`, `countryName`, `emoji`) VALUES ('GH', '가나', '🇬🇭'), ('GA', '가봉', '🇬🇦'), ('GY', '가이아나', '🇬🇾'), From 4f8c38c8d2ded9b3ef9846d7029162bbcd995992 Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Tue, 3 Feb 2026 14:18:26 +0900 Subject: [PATCH 09/12] refactor(tests): Update LoginService and related tests to use loginAndGenerateTokens method * Refactored LoginServiceTest to replace login method with loginAndGenerateTokens. * Updated assertions to reflect changes in response structure. * Adjusted test data and mock setups in LoginControllerTest for consistency. * Removed deprecated init tests in CountryServiceTest as they are now handled by Flyway migrations. * Added Clock mocking in AlarmPolicyServiceTest and AlarmServiceTest for consistent time handling. --- .../controller/LoginControllerTest.java | 95 ++++++++++--------- .../service/AlarmPolicyServiceTest.java | 13 +++ .../tave/memory/service/AlarmServiceTest.java | 14 +++ .../memory/service/CountryServiceTest.java | 29 +----- .../tave/memory/service/LoginServiceTest.java | 38 ++++---- .../service/VisitedCountryServiceTest.java | 12 +-- 6 files changed, 104 insertions(+), 97 deletions(-) diff --git a/src/test/java/zim/tave/memory/controller/LoginControllerTest.java b/src/test/java/zim/tave/memory/controller/LoginControllerTest.java index 6ce9d87..b533d27 100644 --- a/src/test/java/zim/tave/memory/controller/LoginControllerTest.java +++ b/src/test/java/zim/tave/memory/controller/LoginControllerTest.java @@ -26,6 +26,7 @@ import zim.tave.memory.service.KakaoOAuthService; import zim.tave.memory.service.LoginService; +import jakarta.servlet.http.Cookie; import java.util.Optional; import static org.mockito.ArgumentMatchers.any; @@ -65,34 +66,33 @@ class LoginControllerTest { @DisplayName("카카오 로그인 (인가 코드 방식) - 성공") void 카카오_로그인_인가코드_성공() throws Exception { // given - String authCode = "test_auth_code"; + String authCode = "valid_auth_code"; String kakaoAccessToken = "kakao_access_token"; - LoginResponseDto response = new LoginResponseDto( - 1L, - true, - "4317757086", - "http://img1.kakaocdn.net/profile.jpeg", - "jwt_access_token", - "jwt_refresh_token" - ); + User user = new User(); + user.setId(1L); + user.setKakaoId("4317757086"); + user.setProfileImageUrl("http://img1.kakaocdn.net/profile.jpeg"); + user.setRegistered(true); + + String[] tokens = new String[]{"jwt_access_token", "jwt_refresh_token"}; + LoginResponseDto response = LoginResponseDto.from(user, tokens[0], true); when(kakaoOAuthService.getAccessToken(authCode)).thenReturn(kakaoAccessToken); - when(loginService.login(any(LoginRequestDto.class))).thenReturn(response); + when(loginService.loginAndGenerateTokens(any(LoginRequestDto.class))).thenReturn(tokens); + when(loginService.getUserByKakaoAccessToken(kakaoAccessToken)).thenReturn(user); // when & then mockMvc.perform( get("/api/auth/login/kakao") - .param("code", "valid_auth_code") + .param("code", authCode) ) .andExpect(status().isOk()) .andExpect(jsonPath("$.code") .value(ResponseCode.LOGIN_SUCCESS.getCode())) .andExpect(jsonPath("$.data.userId").value(1L)) .andExpect(jsonPath("$.data.accessToken") - .value("jwt_access_token")) - .andExpect(jsonPath("$.data.refreshToken") - .value("jwt_refresh_token")); + .value("jwt_access_token")); } @@ -103,17 +103,18 @@ class LoginControllerTest { // given String kakaoAccessToken = "valid_kakao_token"; - LoginResponseDto response = new LoginResponseDto( - 1L, - true, - "4317757086", - "http://img1.kakaocdn.net/profile.jpeg", - "jwt_access_token", - "jwt_refresh_token" - ); + User user = new User(); + user.setId(1L); + user.setKakaoId("4317757086"); + user.setProfileImageUrl("http://img1.kakaocdn.net/profile.jpeg"); + user.setRegistered(true); + + String[] tokens = new String[]{"jwt_access_token", "jwt_refresh_token"}; + LoginResponseDto response = LoginResponseDto.from(user, tokens[0], true); when(kakaoOAuthService.getAccessToken(any())).thenReturn(kakaoAccessToken); - when(loginService.login(any(LoginRequestDto.class))).thenReturn(response); + when(loginService.loginAndGenerateTokens(any(LoginRequestDto.class))).thenReturn(tokens); + when(loginService.getUserByKakaoAccessToken(kakaoAccessToken)).thenReturn(user); // when & then mockMvc.perform(get("/api/auth/login/kakao") @@ -121,8 +122,7 @@ class LoginControllerTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(ResponseCode.LOGIN_SUCCESS.getCode())) .andExpect(jsonPath("$.data.userId").value(1L)) - .andExpect(jsonPath("$.data.accessToken").value("jwt_access_token")) - .andExpect(jsonPath("$.data.refreshToken").value("jwt_refresh_token")); + .andExpect(jsonPath("$.data.accessToken").value("jwt_access_token")); } @Test @@ -132,17 +132,18 @@ class LoginControllerTest { String authCode = "new_user_code"; String kakaoAccessToken = "new_user_kakao_token"; - LoginResponseDto response = new LoginResponseDto( - 10L, - false, // 앱 가입 미완료 - "9999999999", - "http://new-profile.jpg", - "new_access_token", - "new_refresh_token" - ); + User user = new User(); + user.setId(10L); + user.setKakaoId("9999999999"); + user.setProfileImageUrl("http://new-profile.jpg"); + user.setRegistered(false); + + String[] tokens = new String[]{"new_access_token", "new_refresh_token"}; + LoginResponseDto response = LoginResponseDto.from(user, tokens[0], false); when(kakaoOAuthService.getAccessToken(authCode)).thenReturn(kakaoAccessToken); - when(loginService.login(any(LoginRequestDto.class))).thenReturn(response); + when(loginService.loginAndGenerateTokens(any(LoginRequestDto.class))).thenReturn(tokens); + when(loginService.getUserByKakaoAccessToken(kakaoAccessToken)).thenReturn(user); // when & then mockMvc.perform(get("/api/auth/login/kakao") @@ -159,7 +160,7 @@ class LoginControllerTest { String authCode = "test_code"; when(kakaoOAuthService.getAccessToken(authCode)).thenReturn(""); - when(loginService.login(any())) + when(loginService.loginAndGenerateTokens(any())) .thenThrow(new CustomException(ErrorCode.KAKAO_TOKEN_MISSING)); // when & then @@ -194,15 +195,16 @@ class LoginControllerTest { user.setId(1L); user.setKakaoId("kakao123"); - when(jwtUtil.validateToken(refreshToken)).thenReturn(true); + doNothing().when(jwtUtil).validateTokenWithException(refreshToken); when(jwtUtil.isRefreshToken(refreshToken)).thenReturn(true); when(jwtUtil.getUserIdFromToken(refreshToken)).thenReturn(1L); when(userRepository.findById(1L)).thenReturn(Optional.of(user)); when(jwtUtil.generateAccessToken(1L, "kakao123")).thenReturn("new_access_token"); + when(jwtUtil.generateRefreshToken(1L)).thenReturn("new_refresh_token"); // when & then mockMvc.perform(post("/api/auth/refresh") - .header("Authorization", "Bearer " + refreshToken)) + .cookie(new Cookie("refreshToken", refreshToken))) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(ResponseCode.TOKEN_REFRESH_SUCCESS.getCode())) .andExpect(jsonPath("$.data.accessToken").value("new_access_token")); @@ -214,13 +216,14 @@ class LoginControllerTest { // given String invalidRefreshToken = "invalid_refresh_token"; - when(jwtUtil.validateToken(invalidRefreshToken)).thenReturn(false); + when(jwtUtil.validateTokenWithException(invalidRefreshToken)) + .thenThrow(new CustomException(ErrorCode.INVALID_TOKEN)); // when & then mockMvc.perform(post("/api/auth/refresh") - .header("Authorization", "Bearer " + invalidRefreshToken)) + .cookie(new Cookie("refreshToken", invalidRefreshToken))) .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("$.code").value(500)); // GlobalExceptionHandler가 500을 반환 + .andExpect(jsonPath("$.code").value(ResponseCode.INVALID_TOKEN.getCode())); } @@ -230,23 +233,23 @@ class LoginControllerTest { // given String accessToken = "access_token_not_refresh"; - when(jwtUtil.validateToken(accessToken)).thenReturn(true); + doNothing().when(jwtUtil).validateTokenWithException(accessToken); when(jwtUtil.isRefreshToken(accessToken)).thenReturn(false); // Access Token // when & then mockMvc.perform(post("/api/auth/refresh") - .header("Authorization", "Bearer " + accessToken)) + .cookie(new Cookie("refreshToken", accessToken))) .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("$.code").value(500)); // GlobalExceptionHandler가 500을 반환 + .andExpect(jsonPath("$.code").value(ResponseCode.INVALID_REFRESH_TOKEN.getCode())); } @Test - @DisplayName("토큰 갱신 실패 - Authorization 헤더 누락") + @DisplayName("토큰 갱신 실패 - 쿠키 누락") void 토큰_갱신_실패_헤더누락() throws Exception { // when & then mockMvc.perform(post("/api/auth/refresh")) - .andExpect(status().isInternalServerError()) - .andExpect(jsonPath("$.code").value(500)); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value(ResponseCode.INVALID_REFRESH_TOKEN.getCode())); } @Test diff --git a/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java b/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java index 6a57bb2..8320b3e 100644 --- a/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java +++ b/src/test/java/zim/tave/memory/service/AlarmPolicyServiceTest.java @@ -12,8 +12,10 @@ import zim.tave.memory.repository.AlarmHistoryRepository; import zim.tave.memory.repository.DiaryRepository; +import java.time.Clock; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; @@ -28,6 +30,9 @@ public class AlarmPolicyServiceTest { @Mock private DiaryRepository diaryRepository; + @Mock + private Clock clock; + @InjectMocks private AlarmPolicyService alarmPolicyService; @@ -46,6 +51,14 @@ void setUp() { // totalTripDays = 12 (inclusive), serviceSlotLimit=floor(12*0.25)=3, maxIncompleteSlot=floor(3*0.4)=1 trip.setStartDate(LocalDate.of(2026, 1, 1)); trip.setEndDate(LocalDate.of(2026, 1, 12)); + + // Clock 모킹 설정 - 현재 시간을 고정된 값으로 설정 + Clock fixedClock = Clock.fixed( + LocalDateTime.of(2026, 1, 15, 12, 0).atZone(ZoneId.systemDefault()).toInstant(), + ZoneId.systemDefault() + ); + when(clock.getZone()).thenReturn(ZoneId.systemDefault()); + when(clock.instant()).thenReturn(fixedClock.instant()); } @Test diff --git a/src/test/java/zim/tave/memory/service/AlarmServiceTest.java b/src/test/java/zim/tave/memory/service/AlarmServiceTest.java index 8b478a9..5643131 100644 --- a/src/test/java/zim/tave/memory/service/AlarmServiceTest.java +++ b/src/test/java/zim/tave/memory/service/AlarmServiceTest.java @@ -17,7 +17,10 @@ import zim.tave.memory.repository.AlarmHistoryRepository; import zim.tave.memory.repository.UserRepository; +import java.time.Clock; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -41,6 +44,9 @@ public class AlarmServiceTest { @Mock private UserRepository userRepository; + @Mock + private Clock clock; + @InjectMocks private AlarmService alarmService; @@ -176,6 +182,14 @@ class DecideAlarmType { User user = userWithSetting(userId, true); Trip trip = trip(tripId, LocalDate.now().minusDays(1), LocalDate.now().plusDays(1)); + // Clock 모킹 설정 + Clock fixedClock = Clock.fixed( + LocalDateTime.of(2026, 1, 15, 12, 0).atZone(ZoneId.systemDefault()).toInstant(), + ZoneId.systemDefault() + ); + when(clock.getZone()).thenReturn(ZoneId.systemDefault()); + when(clock.instant()).thenReturn(fixedClock.instant()); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); when(tripService.findCurrentOngoingTrip(userId)).thenReturn(trip); when(alarmPolicyService.decide(user, trip)).thenReturn(AlarmType.DIARY_REMIND); diff --git a/src/test/java/zim/tave/memory/service/CountryServiceTest.java b/src/test/java/zim/tave/memory/service/CountryServiceTest.java index cff8d90..9bbc3b6 100644 --- a/src/test/java/zim/tave/memory/service/CountryServiceTest.java +++ b/src/test/java/zim/tave/memory/service/CountryServiceTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import zim.tave.memory.domain.Country; import zim.tave.memory.repository.CountryRepository; @@ -28,32 +27,8 @@ void clearDB() { countryRepository.findAll().forEach(c -> countryRepository.delete(c)); } - @Test - @Transactional - @Rollback(false) - void init_빈_DB에_정상작동_확인() { - // when - countryService.init(); - // then - List result = countryRepository.findAll(); - assertThat(result).isNotEmpty(); - assertThat(result).anyMatch(c -> c.getCountryCode().equals("KR")); - assertThat(result).anyMatch(c -> c.getCountryCode().equals("US")); - } - - @Test - void init_중복저장_방지_확인() { - // given - countryService.init(); - int firstCount = countryRepository.findAll().size(); - - // when - countryService.init(); // 두 번째 실행 - int secondCount = countryRepository.findAll().size(); - - // then - assertThat(secondCount).isEqualTo(firstCount); // 중복 저장 안 됐는지 확인 - } + // init() 메서드는 Flyway 마이그레이션으로 대체되었으므로 테스트 제거 + // Flyway가 V3__insert_countries.sql을 통해 국가 데이터를 관리합니다. @Test void searchCountryByKeyword() { diff --git a/src/test/java/zim/tave/memory/service/LoginServiceTest.java b/src/test/java/zim/tave/memory/service/LoginServiceTest.java index b3473e9..782723b 100644 --- a/src/test/java/zim/tave/memory/service/LoginServiceTest.java +++ b/src/test/java/zim/tave/memory/service/LoginServiceTest.java @@ -46,7 +46,7 @@ public class LoginServiceTest { request.setAccessToken(null); // when & then - assertThatThrownBy(() -> loginService.login(request)) + assertThatThrownBy(() -> loginService.loginAndGenerateTokens(request)) .isInstanceOf(CustomException.class) .hasMessageContaining(ErrorCode.KAKAO_TOKEN_MISSING.getMessage()); } @@ -54,8 +54,9 @@ public class LoginServiceTest { @Test void 토큰_누락시_예외() { LoginRequestDto request = new LoginRequestDto(""); + request.setAccessToken(""); - assertThatThrownBy(() -> loginService.login(request)) + assertThatThrownBy(() -> loginService.loginAndGenerateTokens(request)) .isInstanceOf(CustomException.class) .hasMessageContaining(ErrorCode.KAKAO_TOKEN_MISSING.getMessage()); } @@ -64,7 +65,8 @@ public class LoginServiceTest { @DisplayName("기존 회원 로그인 - Access Token과 Refresh Token 발급") void 기존회원_로그인() { // given - LoginRequestDto request = new LoginRequestDto("valid_kakao_token"); + LoginRequestDto request = new LoginRequestDto(); + request.setAccessToken("valid_kakao_token"); KakaoUserInfo kakaoInfo = new KakaoUserInfo("kakao123", "http://img.jpg"); User user = new User(); @@ -78,14 +80,13 @@ public class LoginServiceTest { when(jwtUtil.generateRefreshToken(anyLong())).thenReturn("refresh_token_456"); // when - LoginResponseDto response = loginService.login(request); + String[] tokens = loginService.loginAndGenerateTokens(request); // then - assertThat(response.isRegistered()).isTrue(); - assertThat(response.getUserId()).isEqualTo(1L); - assertThat(response.getKakaoId()).isEqualTo("kakao123"); - assertThat(response.getAccessToken()).isEqualTo("access_token_123"); - assertThat(response.getRefreshToken()).isEqualTo("refresh_token_456"); + assertThat(tokens).isNotNull(); + assertThat(tokens.length).isEqualTo(2); + assertThat(tokens[0]).isEqualTo("access_token_123"); + assertThat(tokens[1]).isEqualTo("refresh_token_456"); verify(jwtUtil, times(1)).generateAccessToken(1L, "kakao123"); verify(jwtUtil, times(1)).generateRefreshToken(1L); @@ -95,7 +96,8 @@ public class LoginServiceTest { @DisplayName("신규 회원 로그인 - 카카오 로그인만 완료, 앱 가입 미완료") void 신규회원_로그인() { // given - LoginRequestDto request = new LoginRequestDto("valid_kakao_token"); + LoginRequestDto request = new LoginRequestDto(); + request.setAccessToken("valid_kakao_token"); KakaoUserInfo kakaoInfo = new KakaoUserInfo("kakao999", "http://profile.jpg"); @@ -111,14 +113,13 @@ public class LoginServiceTest { }); // when - LoginResponseDto response = loginService.login(request); + String[] tokens = loginService.loginAndGenerateTokens(request); // then - assertThat(response.isRegistered()).isFalse(); // 앱 가입 미완료 - assertThat(response.getUserId()).isEqualTo(10L); - assertThat(response.getKakaoId()).isEqualTo("kakao999"); - assertThat(response.getAccessToken()).isEqualTo("new_access_token"); - assertThat(response.getRefreshToken()).isEqualTo("new_refresh_token"); + assertThat(tokens).isNotNull(); + assertThat(tokens.length).isEqualTo(2); + assertThat(tokens[0]).isEqualTo("new_access_token"); + assertThat(tokens[1]).isEqualTo("new_refresh_token"); verify(userRepository, times(1)).save(any(User.class)); verify(jwtUtil, times(1)).generateAccessToken(10L, "kakao999"); @@ -173,13 +174,14 @@ public class LoginServiceTest { @DisplayName("카카오 API 호출 실패 시 예외 전파") void 카카오_API_실패() { // given - LoginRequestDto request = new LoginRequestDto("invalid_kakao_token"); + LoginRequestDto request = new LoginRequestDto(); + request.setAccessToken("invalid_kakao_token"); when(kakaoApiClient.getKakaoUserInfo("invalid_kakao_token")) .thenThrow(new CustomException(ErrorCode.KAKAO_INVALID_TOKEN)); // when & then - assertThatThrownBy(() -> loginService.login(request)) + assertThatThrownBy(() -> loginService.loginAndGenerateTokens(request)) .isInstanceOf(CustomException.class) .hasMessageContaining(ErrorCode.KAKAO_INVALID_TOKEN.getMessage()); } diff --git a/src/test/java/zim/tave/memory/service/VisitedCountryServiceTest.java b/src/test/java/zim/tave/memory/service/VisitedCountryServiceTest.java index d215052..86d0f5a 100644 --- a/src/test/java/zim/tave/memory/service/VisitedCountryServiceTest.java +++ b/src/test/java/zim/tave/memory/service/VisitedCountryServiceTest.java @@ -32,7 +32,7 @@ public class VisitedCountryServiceTest { void testRegisterVisitedCountry_SuccessAndUpdate() { // given: 테스트용 데이터 생성 User user = createTestUser("testKakao1"); - Country country = createTestCountry("KR1", "대한민국1", "🇰🇷"); + Country country = createTestCountry("KR", "대한민국1", "🇰🇷"); Emotion emotion1 = createTestEmotion("행복1", "#FFD700"); Emotion emotion2 = createTestEmotion("슬픔1", "#0000FF"); @@ -50,7 +50,7 @@ void testRegisterVisitedCountry_SuccessAndUpdate() { // first는 첫 번째 등록 결과이므로 "행복1"이 맞음 assertThat(first.getEmotionName()).isEqualTo("행복1"); assertThat(updated.getEmotionName()).isEqualTo("슬픔1"); - assertThat(visitedList.get(0).getCountryCode()).isEqualTo("KR1"); + assertThat(visitedList.get(0).getCountryCode()).isEqualTo("KR"); assertThat(visitedList.get(0).getEmotionName()).isEqualTo("슬픔1"); assertThat(visitedList.get(0).getColor()).isEqualTo("#0000FF"); } @@ -59,7 +59,7 @@ void testRegisterVisitedCountry_SuccessAndUpdate() { void testRegisterVisitedCountry_ReturnsSavedEntity() { // given User user = createTestUser("testKakaoNew"); - Country country = createTestCountry("KRNEW", "대한민국NEW", "🇰🇷"); + Country country = createTestCountry("US", "미국", "🇺🇸"); Emotion emotion = createTestEmotion("설렘", "#FDD7DE"); // when @@ -68,7 +68,7 @@ void testRegisterVisitedCountry_ReturnsSavedEntity() { // then assertThat(result.getVisitedCountryId()).isNotNull(); assertThat(result.getUserId()).isEqualTo(user.getId()); - assertThat(result.getCountryCode()).isEqualTo("KRNEW"); + assertThat(result.getCountryCode()).isEqualTo("US"); assertThat(result.getEmotionName()).isEqualTo("설렘"); assertThat(result.getColor()).isEqualTo("#FDD7DE"); } @@ -77,7 +77,7 @@ void testRegisterVisitedCountry_ReturnsSavedEntity() { void testAlreadyVisited() { // given: 테스트용 데이터 생성 User user = createTestUser("testKakao2"); - Country country = createTestCountry("KR2", "대한민국2", "🇰🇷"); + Country country = createTestCountry("JP", "일본", "🇯🇵"); Emotion emotion = createTestEmotion("행복2", "#FFD700"); // when & then: 방문 여부 확인 @@ -94,7 +94,7 @@ void testAlreadyVisited() { void testGetVisitedCountries() { // given: 테스트용 데이터 생성 User user = createTestUser("testKakao8"); - Country country = createTestCountry("KR8", "대한민국8", "🇰🇷"); + Country country = createTestCountry("CN", "중국", "🇨🇳"); Emotion emotion = createTestEmotion("행복8", "#FFD700"); // 방문 국가 등록 From fa3c51d015807579fe7b34540b67d97a34c9b0ab Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Tue, 3 Feb 2026 14:18:37 +0900 Subject: [PATCH 10/12] refactor(tests): Update LoginControllerTest to use doThrow for exception handling * Replaced when().thenThrow() with doThrow().when() for better readability and clarity in the test setup. * This change enhances the exception handling in the test for invalid refresh tokens. --- .../zim/tave/memory/controller/LoginControllerTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/zim/tave/memory/controller/LoginControllerTest.java b/src/test/java/zim/tave/memory/controller/LoginControllerTest.java index b533d27..adbfd66 100644 --- a/src/test/java/zim/tave/memory/controller/LoginControllerTest.java +++ b/src/test/java/zim/tave/memory/controller/LoginControllerTest.java @@ -30,6 +30,8 @@ import java.util.Optional; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -216,8 +218,8 @@ class LoginControllerTest { // given String invalidRefreshToken = "invalid_refresh_token"; - when(jwtUtil.validateTokenWithException(invalidRefreshToken)) - .thenThrow(new CustomException(ErrorCode.INVALID_TOKEN)); + doThrow(new CustomException(ErrorCode.INVALID_TOKEN)) + .when(jwtUtil).validateTokenWithException(invalidRefreshToken); // when & then mockMvc.perform(post("/api/auth/refresh") From b7902adfe87f92e680312d3f3f7498bca69bd889 Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Tue, 3 Feb 2026 14:19:07 +0900 Subject: [PATCH 11/12] =?UTF-8?q?docs=20:=20flayway=5Fmigration=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FLYWAY_MIGRATION_TEST_GUIDE.md | 268 ++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 docs/FLYWAY_MIGRATION_TEST_GUIDE.md diff --git a/docs/FLYWAY_MIGRATION_TEST_GUIDE.md b/docs/FLYWAY_MIGRATION_TEST_GUIDE.md new file mode 100644 index 0000000..8c7a944 --- /dev/null +++ b/docs/FLYWAY_MIGRATION_TEST_GUIDE.md @@ -0,0 +1,268 @@ + +# Flyway 마이그레이션 리팩토링 가이드 + +## 📋 목차 +1. [변경 사항 개요](#변경-사항-개요) +2. [주요 변경 내용](#주요-변경-내용) +3. [데이터베이스 변경 방법](#데이터베이스-변경-방법) +5. [환경별 설정](#환경별-설정) + +--- + +## 변경 사항 개요 + +### 목적 +- **프로덕션 안정성 향상**: JPA의 `ddl-auto: update`를 제거하고 Flyway로 스키마 버전 관리 +- **스키마 버전 관리**: 모든 DDL/DML 변경을 SQL 마이그레이션 파일로 관리 +- **환경 분리**: 개발 환경과 운영 환경의 데이터를 명확히 분리 + +### 변경 전후 비교 + +| 항목 | 변경 전 | 변경 후 | +|------|---------|---------| +| 스키마 관리 | JPA `ddl-auto: update` | Flyway SQL 마이그레이션 | +| 초기 데이터 | Java `DataInitializer`, `CountryInitializer` | SQL `V2__insert_basic_data.sql`, `V3__insert_countries.sql` | +| 테스트 데이터 | Java 코드 | SQL `V4__test_user.sql` (개발 전용) | +| 스키마 검증 | 자동 생성 | `ddl-auto: validate` + Flyway 검증 | + +--- + +## 주요 변경 내용 + +### 1. 의존성 추가 (`build.gradle`) + +```gradle +dependencies { + implementation 'org.flywaydb:flyway-core' + runtimeOnly 'org.flywaydb:flyway-mysql' +} +``` + +### 2. JPA 설정 변경 (`application.yml`) + +**변경 전:** +```yaml +jpa: + hibernate: + ddl-auto: update # 자동 스키마 생성 +``` + +**변경 후:** +```yaml +jpa: + hibernate: + ddl-auto: validate # 스키마 검증만 수행 + properties: + hibernate: + physical_naming_strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + +flyway: + enabled: true + baseline-on-migrate: false + validate-on-migrate: true + locations: + - classpath:db/migration + - classpath:db/migration-dev # 개발 환경 전용 +``` + +### 3. Flyway 마이그레이션 파일 구조 + +``` +src/main/resources/db/ +├── migration/ # 프로덕션 + 개발 환경 공통 +│ ├── V1__init.sql # DDL (테이블 생성) +│ ├── V2__insert_basic_data.sql # 기초 데이터 (테마, 감정, 날씨 등) +│ └── V3__insert_countries.sql # 국가 데이터 (245개) +└── migration-dev/ # 개발 환경 전용 + └── V4__test_user.sql # 테스트 사용자 데이터 +``` + +### 4. 엔티티 매핑 명시화 + +모든 엔티티에 명시적 매핑 추가: + +```java +@Entity +@Table(name = "Diary") // 테이블명 명시 +public class Diary { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) // ID 생성 전략 명시 + @Column(name = "diaryId") // 컬럼명 명시 (camelCase) + private Long id; + + @Lob + @Column(name = "content", nullable = false, length = 88) + private String content; // TINYTEXT로 매핑 +} +``` + +**변경된 엔티티 목록:** +- `User`, `Trip`, `Diary`, `DiaryImage` +- `TripTheme`, `BoardTheme`, `Emotion`, `Weather` +- `Country`, `VisitedCountry`, `Setting` +- `Board`, `BoardStickerMap`, `AlarmHistory` +- `Sticker` + +### 5. 데이터 초기화 클래스 비활성화 + +**변경 전:** +```java +@Component // 자동 실행 +public class DataInitializer implements CommandLineRunner { + // ... +} +``` + +**변경 후:** +```java +// @Component - 비활성화: Flyway 마이그레이션으로 대체됨 +public class DataInitializer implements CommandLineRunner { + // ... +} +``` + +**비활성화된 클래스:** +- `DataInitializer.java` +- `CountryInitializer.java` + +### 6. 컬럼명 및 테이블명 통일 + +- **컬럼명**: 모든 컬럼을 `camelCase`로 통일 (예: `userId`, `createdAt`) +- **테이블명**: 엔티티 클래스명과 일치하도록 변경 (예: `User`, `Diary`, `TripTheme`) + +--- + +## 데이터베이스 변경 방법 + +### 새로운 마이그레이션 파일 생성 + +1. **파일명 규칙**: `V{버전}__{설명}.sql` + - 예: `V5__add_user_email_column.sql` + - 버전은 순차적으로 증가 (V1, V2, V3...) + +2. **파일 위치:** + - 프로덕션 포함: `src/main/resources/db/migration/` + - 개발 전용: `src/main/resources/db/migration-dev/` + +3. **마이그레이션 작성 예시:** + +```sql +-- V5__add_user_email_column.sql +ALTER TABLE `User` +ADD COLUMN `email` VARCHAR(255) AFTER `kakaoId`; + +-- 인덱스 추가 +CREATE INDEX `idx_user_email` ON `User` (`email`); +``` + +### 마이그레이션 실행 + +**자동 실행:** +- 애플리케이션 시작 시 Flyway가 자동으로 미적용 마이그레이션 실행 + +**수동 실행 (선택사항):** +```bash +./gradlew flywayMigrate +``` + +### 마이그레이션 롤백 + +⚠️ **주의**: Flyway는 기본적으로 롤백을 지원하지 않습니다. + +**롤백이 필요한 경우:** +1. 새로운 마이그레이션 파일로 롤백 SQL 작성 + ```sql + -- V6__rollback_email_column.sql + ALTER TABLE `User` DROP COLUMN `email`; + ``` + +2. 또는 데이터베이스를 이전 상태로 복원 (백업 필요) + +### 이미 적용된 마이그레이션 수정 금지 + +❌ **절대 하지 말 것:** +- 이미 적용된 마이그레이션 파일(V1, V2, V3...) 수정 +- Checksum 불일치로 인한 오류 발생 + +✅ **올바른 방법:** +- 새로운 버전의 마이그레이션 파일로 변경사항 추가 + +--- + +## 환경별 설정 + +### 개발 환경 (`application.yml`) + +```yaml +spring: + flyway: + enabled: true + baseline-on-migrate: false + validate-on-migrate: true + locations: + - classpath:db/migration # 공통 마이그레이션 + - classpath:db/migration-dev # 개발 전용 (테스트 데이터) +``` + +**포함되는 마이그레이션:** +- V1: DDL (테이블 생성) +- V2: 기초 데이터 +- V3: 국가 데이터 +- V4: 테스트 사용자 (개발 전용) + +### 운영 환경 (`application-prod.yml`) + +```yaml +spring: + config: + activate: + on-profile: prod + + flyway: + enabled: true + baseline-on-migrate: true # 기존 DB에 Flyway 적용 시 필요 + validate-on-migrate: true + locations: classpath:db/migration # 개발 전용 제외 +``` + +**포함되는 마이그레이션:** +- V1: DDL (테이블 생성) +- V2: 기초 데이터 +- V3: 국가 데이터 +- ❌ V4: 테스트 사용자 (제외됨) + +### 프로파일 활성화 + +**로컬 개발:** +```bash +# 기본 프로파일 (application.yml 사용) +./gradlew bootRun +``` + +**운영 배포:** +```bash +# prod 프로파일 활성화 +java -jar app.jar --spring.profiles.active=prod +``` + +--- + +## 체크리스트 + +### 새로운 스키마 변경 시 + +- [ ] 마이그레이션 파일명이 `V{버전}__{설명}.sql` 형식인가? +- [ ] 버전 번호가 기존 마이그레이션보다 큰가? +- [ ] 개발 전용 데이터인가? → `migration-dev/` 사용 +- [ ] 엔티티에 `@Table`, `@Column` 매핑이 명시되어 있는가? +- [ ] `@GeneratedValue(strategy = GenerationType.IDENTITY)` 설정되어 있는가? +- [ ] 로컬에서 테스트 후 커밋했는가? + +### 배포 전 확인 + +- [ ] `application-prod.yml`에 `baseline-on-migrate: true` 설정되어 있는가? +- [ ] 운영 환경에서 테스트 데이터 마이그레이션이 제외되는가? +- [ ] 데이터베이스 백업을 수행했는가? + + +**마지막 업데이트**: 2026-02-03 From 314365134f60a180b1737f37e2c12598da48ee72 Mon Sep 17 00:00:00 2001 From: Jimin Park Date: Tue, 3 Feb 2026 14:21:28 +0900 Subject: [PATCH 12/12] =?UTF-8?q?chore=20:=20=EC=9B=90=EB=9E=98=20db=20?= =?UTF-8?q?=EB=AA=85=EC=9C=BC=EB=A1=9C=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9b8b152..92ca82a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,7 +6,7 @@ spring: import: optional:application-secret.yml datasource: - url: jdbc:mysql://localhost:3306/memory_db_test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + url: jdbc:mysql://localhost:3306/memory_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: memory_user driver-class-name: com.mysql.cj.jdbc.Driver