99import com .back .domain .roadmap .roadmap .entity .MentorRoadmap ;
1010import com .back .domain .roadmap .roadmap .entity .RoadmapNode ;
1111import com .back .domain .roadmap .roadmap .repository .MentorRoadmapRepository ;
12+ import com .back .domain .roadmap .roadmap .repository .RoadmapNodeRepository ;
1213import com .back .domain .roadmap .task .entity .Task ;
1314import com .back .domain .roadmap .task .repository .TaskRepository ;
1415import com .back .global .exception .ServiceException ;
2829@ Slf4j
2930public class MentorRoadmapService {
3031 private final MentorRoadmapRepository mentorRoadmapRepository ;
32+ private final RoadmapNodeRepository roadmapNodeRepository ;
3133 private final TaskRepository taskRepository ;
3234 private final MentorRepository mentorRepository ;
3335
@@ -54,8 +56,7 @@ public MentorRoadmapSaveResponse create(Long mentorId, MentorRoadmapSaveRequest
5456 List <RoadmapNode > allNodes = createValidatedNodesWithRoadmapId (request .nodes (), mentorRoadmap .getId ());
5557 mentorRoadmap .addNodes (allNodes );
5658
57- // 최종 저장 (노드들 CASCADE INSERT)
58- mentorRoadmap = mentorRoadmapRepository .save (mentorRoadmap );
59+ // CASCADE로 노드들이 자동 저장됨 (추가 save() 호출 불필요)
5960
6061 log .info ("멘토 로드맵 생성 완료 - 멘토 ID: {}, 로드맵 ID: {}, 노드 수: {} (cascade 활용)" ,
6162 mentorId , mentorRoadmap .getId (), mentorRoadmap .getNodes ().size ());
@@ -83,7 +84,7 @@ public MentorRoadmapResponse getById(Long id) {
8384 // 멘토 ID로 멘토 로드맵 상세 조회 (미래 API 확장성 대비)
8485 @ Transactional (readOnly = true )
8586 public MentorRoadmapResponse getByMentorId (Long mentorId ) {
86- // 멘토 ID로 로드맵과 노드들을 한 번에 조회 (성능 최적화)
87+ // 멘토 ID로 로드맵과 노드들을 한 번에 조회
8788 MentorRoadmap mentorRoadmap = mentorRoadmapRepository .findByMentorIdWithNodes (mentorId )
8889 .orElseThrow (() -> new ServiceException ("404" , "해당 멘토의 로드맵을 찾을 수 없습니다." ));
8990
@@ -94,7 +95,7 @@ public MentorRoadmapResponse getByMentorId(Long mentorId) {
9495 @ Transactional
9596 public MentorRoadmapSaveResponse update (Long id , Long mentorId , MentorRoadmapSaveRequest request ) {
9697 // 수정하려는 로드맵이 실제로 있는지 확인
97- MentorRoadmap mentorRoadmap = mentorRoadmapRepository .findByIdWithNodes (id )
98+ MentorRoadmap mentorRoadmap = mentorRoadmapRepository .findById (id )
9899 .orElseThrow (() -> new ServiceException ("404" , "로드맵을 찾을 수 없습니다." ));
99100
100101 // 권한 확인 - 본인의 로드맵만 수정 가능
@@ -109,8 +110,13 @@ public MentorRoadmapSaveResponse update(Long id, Long mentorId, MentorRoadmapSav
109110 mentorRoadmap .updateTitle (request .title ());
110111 mentorRoadmap .updateDescription (request .description ());
111112
112- // 기존 노드 제거 후 roadmapId를 포함한 새 노드들 추가
113- mentorRoadmap .clearNodes ();
113+ // 1. 기존 노드들을 DB에서 직접 삭제
114+ roadmapNodeRepository .deleteByRoadmapIdAndRoadmapType (
115+ mentorRoadmap .getId (),
116+ RoadmapNode .RoadmapType .MENTOR
117+ );
118+
119+ // 2. 새 노드들 생성 및 추가
114120 List <RoadmapNode > allNodes = createValidatedNodesWithRoadmapId (request .nodes (), mentorRoadmap .getId ());
115121 mentorRoadmap .addNodes (allNodes );
116122
@@ -141,7 +147,13 @@ public void delete(Long roadmapId, Long mentorId) {
141147 throw new ServiceException ("403" , "본인의 로드맵만 삭제할 수 있습니다." );
142148 }
143149
144- // cascade로 자동으로 관련 노드들도 함께 삭제됨
150+ // 1. 관련 노드들을 먼저 직접 삭제
151+ roadmapNodeRepository .deleteByRoadmapIdAndRoadmapType (
152+ roadmapId ,
153+ RoadmapNode .RoadmapType .MENTOR
154+ );
155+
156+ // 2. 로드맵 삭제
145157 mentorRoadmapRepository .delete (mentorRoadmap );
146158
147159 log .info ("멘토 로드맵 삭제 완료 - 멘토 ID: {}, 로드맵 ID: {}" , mentorId , roadmapId );
@@ -157,26 +169,32 @@ private void validateRequest(MentorRoadmapSaveRequest request) {
157169
158170 // stepOrder 연속성 검증 (멘토 로드맵은 선형 구조)
159171 private void validateStepOrderSequence (List <RoadmapNodeRequest > nodes ) {
160- List <Integer > stepOrders = nodes .stream ()
161- .map (RoadmapNodeRequest ::stepOrder )
162- .toList ();
172+ int nodeCount = nodes .size ();
173+ boolean [] stepExists = new boolean [nodeCount + 1 ]; // 1부터 nodeCount까지 사용
163174
164- // 중복 검증 먼저 수행
165- long distinctCount = stepOrders .stream ().distinct ().count ();
166- if (distinctCount != stepOrders .size ()) {
167- throw new ServiceException ("400" , "stepOrder에 중복된 값이 있습니다." );
168- }
175+ // 중복 검증 및 stepOrder 수집
176+ for (RoadmapNodeRequest node : nodes ) {
177+ int stepOrder = node .stepOrder ();
178+
179+ // 범위 검증
180+ if (stepOrder < 1 || stepOrder > nodeCount ) {
181+ throw new ServiceException ("400" ,
182+ String .format ("stepOrder는 1부터 %d 사이의 값이어야 합니다." , nodeCount ));
183+ }
184+
185+ // 중복 검증
186+ if (stepExists [stepOrder ]) {
187+ throw new ServiceException ("400" , "stepOrder에 중복된 값이 있습니다" );
188+ }
169189
170- // 정렬 후 연속성 검증
171- List < Integer > sortedStepOrders = stepOrders . stream (). sorted (). toList ();
190+ stepExists [ stepOrder ] = true ;
191+ }
172192
173- // 1부터 시작하는 연속된 숫자인지 검증
174- for (int i = 0 ; i < sortedStepOrders .size (); i ++) {
175- int expectedOrder = i + 1 ;
176- if (!sortedStepOrders .get (i ).equals (expectedOrder )) {
193+ // 연속성 검증 (1부터 nodeCount까지 모든 값이 존재하는지 확인)
194+ for (int i = 1 ; i <= nodeCount ; i ++) {
195+ if (!stepExists [i ]) {
177196 throw new ServiceException ("400" ,
178- String .format ("stepOrder는 1부터 시작하는 연속된 숫자여야 합니다. 현재: %s, 기대값: %d" ,
179- sortedStepOrders , expectedOrder ));
197+ String .format ("stepOrder는 1부터 시작하는 연속된 숫자여야 합니다." ));
180198 }
181199 }
182200 }
0 commit comments