Skip to content

Commit 7f4b0bd

Browse files
authored
refactor: 내 모든 텃밭 id 조회 (#161)
* refactor: 내 모든 텃밭 id 조회 * refactor: 에러코드 추가
1 parent 62cb5e5 commit 7f4b0bd

3 files changed

Lines changed: 40 additions & 45 deletions

File tree

src/main/java/com/example/cp_main_be/domain/member/user/presentation/UserController.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.example.cp_main_be.domain.member.user.presentation;
22

3-
import com.example.cp_main_be.domain.garden.garden.domain.Garden;
43
import com.example.cp_main_be.domain.member.user.domain.User;
54
import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository;
65
import com.example.cp_main_be.domain.member.user.dto.request.AvatarChangeRequest;
@@ -13,9 +12,7 @@
1312
import io.swagger.v3.oas.annotations.Operation;
1413
import io.swagger.v3.oas.annotations.tags.Tag;
1514
import jakarta.validation.Valid;
16-
import java.util.Comparator;
1715
import java.util.List;
18-
import java.util.stream.Collectors;
1916
import lombok.RequiredArgsConstructor;
2017
import org.springframework.http.ResponseEntity;
2118
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -89,12 +86,8 @@ public ResponseEntity<ApiResponse<UserRegisterResponse.LevelStatusResponseDTO>>
8986
@GetMapping("/me/gardens")
9087
public ResponseEntity<ApiResponse<List<Long>>> getMyGardenIds(
9188
@AuthenticationPrincipal User user) {
92-
List<Long> gardenIds =
93-
user.getGardens().stream()
94-
.sorted(Comparator.comparing(Garden::getSlotNumber))
95-
.map(Garden::getId)
96-
.collect(Collectors.toList());
97-
89+
// [수정] 서비스 계층으로 로직을 위임하여 트랜잭션 내에서 안전하게 데이터를 조회합니다.
90+
List<Long> gardenIds = userService.getMyGardenIds(user);
9891
return ResponseEntity.ok(ApiResponse.success(gardenIds));
9992
}
10093

src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,9 @@
1616
import com.example.cp_main_be.domain.social.follow.domain.repository.FollowRepository;
1717
import com.example.cp_main_be.global.common.CustomApiException;
1818
import com.example.cp_main_be.global.common.ErrorCode;
19-
import com.example.cp_main_be.global.exception.AvatarNotFoundException;
20-
import com.example.cp_main_be.global.exception.UserNotFoundException;
2119
import java.time.LocalDateTime;
2220
import java.time.ZoneId;
23-
import java.util.Comparator;
24-
import java.util.List;
25-
import java.util.Objects;
26-
import java.util.UUID;
21+
import java.util.*;
2722
import java.util.stream.Collectors;
2823
import lombok.RequiredArgsConstructor;
2924
import org.springframework.security.core.Authentication;
@@ -36,6 +31,8 @@
3631
@Transactional
3732
public class UserService {
3833

34+
private static final int MAX_FRIEND_WATERING_PER_DAY = 3;
35+
3936
private final UserRepository userRepository;
4037
private final LevelService levelService;
4138
private final AvatarRepository avatarRepository;
@@ -45,8 +42,8 @@ public class UserService {
4542
public void addExperience(Long actorId, int points) {
4643
User user =
4744
userRepository
48-
.findById(actorId)
49-
.orElseThrow(() -> new UserNotFoundException(ErrorCode.USER_NOT_FOUND.getMessage()));
45+
.findById(actorId) // ID로 최신 유저 정보를 조회합니다.
46+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
5047
user.addExperience(points);
5148
levelService.checkLevelUp(user);
5249
}
@@ -59,7 +56,7 @@ public void updateAvatar(User user, AvatarChangeRequest request, Long avatarId)
5956
Avatar avatar =
6057
avatarRepository
6158
.findById(avatarId)
62-
.orElseThrow(() -> new AvatarNotFoundException("아바타를 찾을 수 없습니다."));
59+
.orElseThrow(() -> new CustomApiException(ErrorCode.AVATAR_NOT_FOUND));
6360

6461
// [버그 수정] AvatarMaster(원본)가 아닌 Avatar(개별 인스턴스)의 imageUrl을 변경해야 합니다.
6562
// Avatar 엔티티에 imageUrl 필드가 있어야 합니다.
@@ -74,8 +71,12 @@ public void updateAvatar(User user, AvatarChangeRequest request, Long avatarId)
7471
}
7572

7673
public void updateNickname(User user, String newNickname) {
77-
78-
user.updateProfile(newNickname, null);
74+
// [수정] stale한 user 객체 대신, ID로 최신 정보를 조회해서 사용합니다.
75+
User managedUser =
76+
userRepository
77+
.findById(user.getId())
78+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
79+
managedUser.updateProfile(newNickname, null);
7980
userRepository.save(user);
8081
}
8182

@@ -86,21 +87,21 @@ public void saveUser(User user) {
8687
public void deleteUser(Long userId) {
8788
User user =
8889
userRepository
89-
.findById(userId)
90-
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
90+
.findById(userId) // ID로 최신 유저 정보를 조회합니다.
91+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
9192
userRepository.delete(user);
9293
}
9394

9495
public User findUserById(Long id) {
9596
return this.userRepository
9697
.findById(id)
97-
.orElseThrow(() -> new UserNotFoundException("해당 ID의 사용자를 찾을 수 없습니다 : " + id));
98+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
9899
}
99100

100101
public User findUserByUuid(UUID uuid) {
101102
return this.userRepository
102103
.findByUuid(uuid)
103-
.orElseThrow(() -> new UserNotFoundException("해당 UUID의 사용자를 찾을 수 없습니다 : " + uuid));
104+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
104105
}
105106

106107
public List<User> findAllUsers() {
@@ -128,11 +129,11 @@ public User getCurrentUser() {
128129
if (principal instanceof String uuidString) {
129130
UUID userUuid = UUID.fromString(uuidString);
130131
return userRepository
131-
.findByUuid(userUuid)
132-
.orElseThrow(() -> new IllegalArgumentException("현재 로그인한 사용자를 찾을 수 없습니다."));
132+
.findByUuid(userUuid) // UUID로 최신 유저 정보를 조회합니다.
133+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
133134
}
134135

135-
throw new IllegalArgumentException("인증 정보를 찾을 수 없습니다.");
136+
throw new CustomApiException(ErrorCode.INVALID_TOKEN, "인증 정보를 찾을 수 없습니다.");
136137
}
137138

138139
/**
@@ -147,9 +148,10 @@ public List<Long> getMyGardenIds(User user) {
147148
User managedUser =
148149
userRepository
149150
.findByIdWithGardens(user.getId())
150-
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
151+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
151152

152153
return managedUser.getGardens().stream()
154+
.filter(garden -> !garden.isLocked()) // [추가] 잠겨있지 않은(isLocked=false) 텃밭만 필터링합니다.
153155
.sorted(Comparator.comparing(Garden::getSlotNumber))
154156
.map(Garden::getId)
155157
.collect(Collectors.toList());
@@ -165,12 +167,12 @@ public List<Long> getMyGardenIds(User user) {
165167
public UserProfileResponse getUserProfile(Long currentUserId, Long profileUserId) {
166168
User currentUser =
167169
userRepository
168-
.findById(currentUserId)
169-
.orElseThrow(() -> new CustomApiException(ErrorCode.NOT_FOUND));
170+
.findById(currentUserId) // ID로 최신 유저 정보를 조회합니다.
171+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
170172
User profileUser =
171173
userRepository
172174
.findByIdWithGardensAndAvatars(profileUserId)
173-
.orElseThrow(() -> new CustomApiException(ErrorCode.NOT_FOUND));
175+
.orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND));
174176

175177
// [수정] 프로필 이미지 URL을 사용자의 첫 번째 아바타 이미지로 설정
176178
String profileImageUrl =
@@ -210,29 +212,28 @@ public UserProfileResponse getUserProfile(Long currentUserId, Long profileUserId
210212
int todayWateringCountForOthers =
211213
friendWateringLogRepository.countByWaterGiverAndWateredAtAfter(
212214
currentUser, startOfWateringDay);
213-
Long leftWaterCountForOthers =
214-
(long) (3 - todayWateringCountForOthers); // MAX_FRIEND_WATERING_PER_DAY = 3
215+
long leftWaterCountForOthers =
216+
Math.max(0, (long) MAX_FRIEND_WATERING_PER_DAY - todayWateringCountForOthers);
217+
218+
// [성능 개선] N+1 문제를 해결하기 위해, 오늘 내가 물 준 정원 ID 목록을 한 번에 조회합니다.
219+
Set<Long> wateredGardenIds =
220+
friendWateringLogRepository.findWateredGardenIdsByGiverAndDate(
221+
currentUser.getId(), startOfWateringDay);
215222

216223
// 3. 프로필 주인의 정원 목록 및 물주기 가능 여부 계산
217224
List<UserGardenDetailResponse> userGardens =
218225
profileUser.getGardens().stream()
226+
.sorted(Comparator.comparing(Garden::getSlotNumber))
219227
.map(
220228
garden -> {
221-
// 현재 접속 유저가 이 정원에 오늘 물을 줄 수 있는지 여부
222-
boolean isWateringAbleByMe = false;
223-
// 조건: 1) 아직 오늘 남에게 물 줄 수 있는 횟수가 남아있어야 하고 (leftWaterCountForOthers > 0)
224-
// 2) 오늘 이 정원에 내가 물을 준 적이 없어야 한다.
225-
if (leftWaterCountForOthers > 0) {
226-
boolean alreadyWateredByMe =
227-
friendWateringLogRepository
228-
.existsByWaterGiverAndWateredGardenAndWateredAtAfter(
229-
currentUser, garden, startOfWateringDay);
230-
isWateringAbleByMe = !alreadyWateredByMe;
231-
}
229+
// DB를 반복 조회하는 대신, 미리 조회한 Set에서 확인하여 성능을 개선합니다.
230+
boolean alreadyWateredByMe = wateredGardenIds.contains(garden.getId());
231+
boolean isWateringAbleByMe = leftWaterCountForOthers > 0 && !alreadyWateredByMe;
232232

233233
HomeResponseDto.AvatarInfo avatarInfoForGarden =
234234
HomeResponseDto.AvatarInfo.builder()
235-
.avatarId(garden.getAvatar().getId())
235+
// 아바타가 없는 텃밭이 있을 수 있는 예외 케이스를 방어합니다.
236+
.avatarId(garden.getAvatar() != null ? garden.getAvatar().getId() : null)
236237
.avatarName(garden.getAvatar().getNickname())
237238
.avatarImageUrl(garden.getAvatar().getAvatarMaster().getDefaultImageUrl())
238239
.build();

src/main/java/com/example/cp_main_be/global/common/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public enum ErrorCode {
4141
AVATAR_MASTER_NOT_FOUND(HttpStatus.NOT_FOUND, "E40408", "해당 아바타 원본을 찾을 수 없습니다."),
4242
WISH_TREE_NOT_FOUND(HttpStatus.NOT_FOUND, "E40409", "소원나무 정보를 찾을 수 없습니다."),
4343
DEFAULT_RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "E40410", "필수 기본 리소스를 찾을 수 없습니다."),
44+
AVATAR_NOT_FOUND(HttpStatus.NOT_FOUND, "E40411", "해당 아바타를 찾을 수 없습니다."),
4445

4546
// 417 Expectation Failed
4647
UPLOAD_FAILED(HttpStatus.EXPECTATION_FAILED, "E41701", "파일 업로드에 실패했습니다."),

0 commit comments

Comments
 (0)