Skip to content

Commit ca81e2d

Browse files
author
Kim DongMin
committed
hotfix: 게임 DB 방식 ENUM 방식으로 전환
1 parent 3e919e2 commit ca81e2d

9 files changed

Lines changed: 73 additions & 101 deletions

File tree

src/main/java/com/web/coreclass/domain/game/entity/Game.java

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.web.coreclass.domain.game.entity;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
6+
@Getter
7+
@RequiredArgsConstructor
8+
public enum GameType {
9+
OVERWATCH_2("Overwatch 2", "https://second-generation-upload-bucket.s3.ap-northeast-2.amazonaws.com/overwatch2_logo.png"),
10+
VALORANT("Valorant", "https://second-generation-upload-bucket.s3.ap-northeast-2.amazonaws.com/valorant_logo.png");
11+
12+
private final String name; // DB 저장용 이름 or 화면 표시용
13+
private final String logoUrl; // 로고 이미지 URL
14+
15+
// 문자열(String)로 Enum을 찾는 메서드 추가
16+
public static GameType fromName(String name) {
17+
for (GameType type : values()) {
18+
// 대소문자 무시하고 띄어쓰기 제거 후 비교 등 유연하게 처리 가능
19+
if (type.name.equalsIgnoreCase(name) || type.name().equalsIgnoreCase(name)) {
20+
return type;
21+
}
22+
}
23+
throw new IllegalArgumentException("지원하지 않는 게임입니다: " + name);
24+
}
25+
}

src/main/java/com/web/coreclass/domain/game/repository/GameRepository.java

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/main/java/com/web/coreclass/domain/instructor/dto/InstructorDto.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.web.coreclass.domain.careerHistory.entity.CareerHistory;
44
import com.web.coreclass.domain.careerHistory.entity.RoleType;
5-
import com.web.coreclass.domain.game.entity.Game;
5+
import com.web.coreclass.domain.game.entity.GameType;
66
import com.web.coreclass.domain.instructor.entity.Instructor;
77
import io.swagger.v3.oas.annotations.media.Schema;
88
import lombok.Getter;
@@ -107,18 +107,15 @@ public CareerHistoryResponse(CareerHistory history) {
107107
}
108108
}
109109

110-
// 게임 상세 DTO (Nested)
111-
@Getter
112-
@ToString
110+
// GameResponse
113111
public static class GameResponse {
114-
private Long id;
115112
private String name;
116113
private String gameLogoUrl;
117114

118-
public GameResponse(Game game) {
119-
this.id = game.getId();
120-
this.name = game.getName();
121-
this.gameLogoUrl = game.getGameLogoUrl();
115+
// 생성자 파라미터 변경: Game -> GameType (또는 InstructorGame)
116+
public GameResponse(GameType gameType) {
117+
this.name = gameType.getName(); // Enum의 한글/영문 이름
118+
this.gameLogoUrl = gameType.getLogoUrl(); // Enum에 정의된 로고 URL
122119
}
123120
}
124121

@@ -137,8 +134,9 @@ public InstructorDetailResponse(Instructor instructor) {
137134
.map(CareerHistoryResponse::new)
138135
.collect(Collectors.toSet());
139136

137+
// InstructorDetailResponse 생성자 내부 수정
140138
this.games = instructor.getGames().stream()
141-
.map(instructorGame -> new GameResponse(instructorGame.getGame()))
139+
.map(instructorGame -> new GameResponse(instructorGame.getGameType())) // 💡 수정
142140
.collect(Collectors.toSet());
143141
}
144142
}
@@ -165,9 +163,8 @@ public InstructorListResponse(Instructor instructor) {
165163
this.profileImgUrl = instructor.getProfileImgUrl();
166164
this.sgeaLogoImgUrl = instructor.getSgeaLogoImgUrl();
167165

168-
// ✅ N+1 문제를 피하려면, Service에서 Fetch Join이 필요함
169166
this.games = instructor.getGames().stream()
170-
.map(instructorGame -> new InstructorDetailResponse.GameResponse(instructorGame.getGame()))
167+
.map(instructorGame -> new InstructorDetailResponse.GameResponse(instructorGame.getGameType())) // 💡 수정
171168
.collect(Collectors.toSet());
172169
}
173170
}

src/main/java/com/web/coreclass/domain/instructor/entity/InstructorGame.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.web.coreclass.domain.instructor.entity;
22

3-
import com.web.coreclass.domain.game.entity.Game;
3+
import com.web.coreclass.domain.game.entity.GameType;
44
import com.web.coreclass.global.entity.BaseEntity;
55
import jakarta.persistence.*;
66
import lombok.Getter;
@@ -23,7 +23,7 @@ public class InstructorGame extends BaseEntity {
2323
private Instructor instructor;
2424

2525
// InstructorGame(N) : Game(1)
26-
@ManyToOne(fetch = FetchType.LAZY)
27-
@JoinColumn(name = "game_id", nullable = false)
28-
private Game game;
26+
@Enumerated(EnumType.STRING) // DB에 "VALORANT" 문자열로 저장됨
27+
@Column(name = "game_type", nullable = false)
28+
private GameType gameType;
2929
}

src/main/java/com/web/coreclass/domain/instructor/repository/InstructorRepository.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@ public interface InstructorRepository extends JpaRepository<Instructor, Long> {
1414
@Query("SELECT i FROM Instructor i " +
1515
"LEFT JOIN FETCH i.careerHistories " +
1616
"LEFT JOIN FETCH i.games ig " +
17-
"LEFT JOIN FETCH ig.game " +
1817
"WHERE i.id = :id")
1918
Optional<Instructor> findInstructorDetailsById(@Param("id") Long id);
2019

2120
// 록 조회용 N+1 방지 쿼리 (games만 Join)
2221
@Query("SELECT i FROM Instructor i " +
23-
"LEFT JOIN FETCH i.games ig " +
24-
"LEFT JOIN FETCH ig.game")
22+
"LEFT JOIN FETCH i.games ig")
2523
List<Instructor> findAllWithGames();
2624
}

src/main/java/com/web/coreclass/domain/instructor/service/InstructorService.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.web.coreclass.domain.instructor.service;
22

3-
import com.web.coreclass.domain.game.entity.Game;
4-
import com.web.coreclass.domain.game.repository.GameRepository;
3+
import com.web.coreclass.domain.game.entity.GameType;
54
import com.web.coreclass.domain.instructor.dto.InstructorDto;
65
import com.web.coreclass.domain.instructor.entity.Instructor;
76
import com.web.coreclass.domain.instructor.entity.InstructorGame;
@@ -19,7 +18,6 @@
1918
public class InstructorService {
2019

2120
private final InstructorRepository instructorRepository;
22-
private final GameRepository gameRepository;
2321

2422
/**
2523
* (C) Create: 강사 생성
@@ -41,15 +39,12 @@ public InstructorDto.InstructorDetailResponse createInstructor(InstructorDto.Ins
4139

4240
// 3. Game 엔티티 조회 및 InstructorGame 매핑 (Cascade)
4341
request.getGameNames().forEach(gameName -> {
44-
// DB에서 게임 이름으로 Game 엔티티 조회
45-
Game game = gameRepository.findByName(gameName)
46-
.orElseThrow(() -> new RuntimeException("Game not found: " + gameName));
42+
// 💡 DB 조회가 아니라 Enum에서 바로 변환 (에러 걱정 없음)
43+
GameType gameType = GameType.fromName(gameName);
4744

48-
// InstructorGame 매핑 엔티티 생성
4945
InstructorGame instructorGame = new InstructorGame();
50-
instructorGame.setGame(game);
46+
instructorGame.setGameType(gameType); // 💡 setGame -> setGameType
5147

52-
// 연관관계 편의 메서드 사용
5348
instructor.addGame(instructorGame);
5449
});
5550

@@ -115,10 +110,12 @@ public void updateInstructor(Long id, InstructorDto.InstructorCreateRequest requ
115110
});
116111

117112
request.getGameNames().forEach(gameName -> {
118-
Game game = gameRepository.findByName(gameName)
119-
.orElseThrow(() -> new RuntimeException("Game not found:" + "Z" + gameName));
120-
InstructorGame instructorGame = new InstructorGame();
121-
instructorGame.setGame(game);
113+
// 💡 DB 조회가 아니라 Enum에서 바로 변환 (에러 걱정 없음)
114+
GameType gameType = GameType.fromName(gameName);
115+
116+
InstructorGame instructorGame = new InstructorGame();
117+
instructorGame.setGameType(gameType); // 💡 setGame -> setGameType
118+
122119
instructor.addGame(instructorGame);
123120
});
124121
}

src/main/resources/application.properties

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ spring.application.name=coreclass
33
# DDL ??
44
spring.jpa.hibernate.ddl-auto=update
55

6+
## 1. H2 DB ?? ?? (MySQL ??)
7+
#spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1
8+
#spring.datasource.driver-class-name=org.h2.Driver
9+
#spring.datasource.username=sa
10+
#spring.datasource.password=
11+
#
12+
## === AWS S3 Test Setting (Dummy) ===
13+
#spring.cloud.aws.s3.bucket=test-bucket
14+
#spring.cloud.aws.region.static=ap-northeast-2
15+
#spring.cloud.aws.credentials.access-key=test-access-key
16+
#spring.cloud.aws.credentials.secret-key=test-secret-key
17+
618
# ==========================================
719
# ? Database Setting (???? ??)
820
# ==========================================
@@ -22,14 +34,17 @@ spring.cloud.aws.region.static=ap-northeast-2
2234
spring.cloud.aws.credentials.access-key=${AWS_ACCESS_KEY}
2335
spring.cloud.aws.credentials.secret-key=${AWS_SECRET_KEY}
2436

37+
38+
2539
# File Upload Limit
2640
spring.servlet.multipart.max-file-size=10MB
2741
spring.servlet.multipart.max-request-size=10MB
2842

2943
# ==========================================
3044
# ? JWT Setting (???? ??)
3145
# ==========================================
32-
jwt.secret=${JWT_SECRET}
46+
#jwt.secret=${JWT_SECRET}
47+
jwt.secret=TestSecretKeyForUnitTestingMustBeLongEnoughToPreventErrors1234567890
3348
jwt.expiration-ms=3600000
3449

3550
# Swagger Config (?? ?? ??)

src/test/java/com/web/coreclass/InstructorServiceTest.java

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.web.coreclass;
22

33
import com.web.coreclass.domain.careerHistory.entity.RoleType;
4-
import com.web.coreclass.domain.game.entity.Game;
5-
import com.web.coreclass.domain.game.repository.GameRepository;
64
import com.web.coreclass.domain.instructor.dto.InstructorDto;
75
import com.web.coreclass.domain.instructor.entity.Instructor;
86
import com.web.coreclass.domain.instructor.repository.InstructorRepository;
@@ -29,26 +27,9 @@ public class InstructorServiceTest {
2927
@Autowired
3028
private InstructorRepository instructorRepository;
3129

32-
@Autowired
33-
private GameRepository gameRepository;
34-
3530
@Autowired
3631
private EntityManager em; // 영속성 컨텍스트 관리 (캐시 비우기용)
3732

38-
// (Given) 각 테스트 실행 전에 게임 데이터를 미리 세팅
39-
@BeforeEach
40-
void setup() {
41-
// 1. "Valorant" 생성
42-
Game valorant = new Game();
43-
valorant.setName("Valorant");
44-
gameRepository.save(valorant);
45-
46-
// 2. "League of Legends" 생성
47-
Game lol = new Game();
48-
lol.setName("League of Legends");
49-
gameRepository.save(lol);
50-
}
51-
5233
@Test
5334
@DisplayName("강사 생성(C): 경력 및 게임 정보를 포함하여 성공적으로 생성된다.")
5435
void createInstructorTest() {
@@ -73,7 +54,7 @@ void createInstructorTest() {
7354
request.setSgeaLogoImgUrl("sgea_logo.png");
7455
request.setContent("메이저 리그 출신...");
7556
request.setCareers(List.of(career1, career2));
76-
request.setGameNames(List.of("Valorant", "League of Legends")); // setup에서 저장한 게임 이름
57+
request.setGameNames(List.of("Valorant", "Overwatch 2")); // setup에서 저장한 게임 이름
7758

7859
// sout 대신 log.info() 사용
7960
// 중괄호 {}를 사용하면 파라미터가 효율적으로 전달됩니다.
@@ -121,8 +102,8 @@ void createInstructorTest() {
121102
assertThat(findInstructor.getGames()).hasSize(2);
122103
// Set에서 InstructorGame을 꺼내고, 다시 Game을 꺼내서 Name을 추출
123104
assertThat(findInstructor.getGames())
124-
.extracting(instructorGame -> instructorGame.getGame().getName())
125-
.containsExactlyInAnyOrder("Valorant", "League of Legends");
105+
.extracting(ig -> ig.getGameType().getName())
106+
.containsExactlyInAnyOrder("Valorant", "Overwatch 2");
126107

127108
log.info("===== ✅ 강사 생성(C) 테스트 통과 =====");
128109
}
@@ -159,7 +140,7 @@ void getInstructorListTest() {
159140
request2.setSgeaLogoImgUrl("sgea_logo2.png");
160141
request2.setContent("LCK 출신...");
161142
request2.setCareers(List.of(career2));
162-
request2.setGameNames(List.of("League of Legends", "Valorant")); // 2개 게임
143+
request2.setGameNames(List.of("Overwatch 2", "Valorant")); // 2개 게임
163144
instructorService.createInstructor(request2); // (반환값 안씀)
164145

165146
// 💡 중요: 영속성 컨텍스트 초기화 (Fetch Join 쿼리 테스트를 위해)
@@ -196,7 +177,7 @@ void getInstructorListTest() {
196177
assertThat(aka.getGames()).hasSize(2);
197178
assertThat(aka.getGames())
198179
.extracting("name") // GameResponse DTO의 'name' 필드
199-
.containsExactlyInAnyOrder("League of Legends", "Valorant");
180+
.containsExactlyInAnyOrder("Overwatch 2", "Valorant");
200181

201182
log.info("===== ✅ 강사 목록(R-List) 테스트 통과 =====");
202183
}
@@ -307,7 +288,7 @@ void updateInstructorTest() {
307288
updateRequest.setNickname("Rexi-Updated"); // ⬅️ 닉네임 수정
308289
updateRequest.setContent("수정 완료 본문");
309290
updateRequest.setCareers(List.of(updatedCareer1, updatedCareer2)); // ⬅️ 경력 2개로 변경
310-
updateRequest.setGameNames(List.of("League of Legends")); // ⬅️ 게임 변경
291+
updateRequest.setGameNames(List.of("Overwatch 2")); // ⬅️ 게임 변경
311292

312293
// --- When (실행) ---
313294
log.info("🚀 instructorService.updateInstructor({}) 호출", instructorId);
@@ -338,8 +319,8 @@ void updateInstructorTest() {
338319
// 3. ⭐️ 게임(Collection) 덮어쓰기 검증 ⭐️
339320
assertThat(updatedInstructor.getGames()).hasSize(1);
340321
assertThat(updatedInstructor.getGames())
341-
.extracting(ig -> ig.getGame().getName())
342-
.containsExactly("League of Legends");
322+
.extracting(ig -> ig.getGameType().getName())
323+
.containsExactly("Overwatch 2");
343324
// ➡️ "Valorant"가 삭제되었는지 검증
344325

345326
log.info("===== ✅ 강사 수정(U) 테스트 통과 =====");

0 commit comments

Comments
 (0)