Skip to content

Commit b308966

Browse files
committed
fix: 유저 수정 기능 수정
- Spotify 중복 저장 수정
1 parent 6e78a10 commit b308966

File tree

6 files changed

+69
-9
lines changed

6 files changed

+69
-9
lines changed
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.dfbf.soundlink.domain.emotionRecord.repository;
22

33
import org.dfbf.soundlink.domain.emotionRecord.entity.SpotifyMusic;
4+
import org.dfbf.soundlink.domain.emotionRecord.repository.dsl.SpotifyMusicRepositoryCustom;
45
import org.springframework.data.jpa.repository.JpaRepository;
56
import org.springframework.stereotype.Repository;
67

8+
import java.util.List;
79
import java.util.Optional;
810

911
@Repository
10-
public interface SpotifyMusicRepository extends JpaRepository<SpotifyMusic, Long> {
12+
public interface SpotifyMusicRepository extends JpaRepository<SpotifyMusic, Long>, SpotifyMusicRepositoryCustom {
1113
Optional<SpotifyMusic> findBySpotifyId(String spotifyId);
1214
}

src/main/java/org/dfbf/soundlink/domain/emotionRecord/repository/dsl/EmotionRecordRepositoryCustom.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.dfbf.soundlink.domain.emotionRecord.repository.dsl;
22

33
import org.dfbf.soundlink.domain.emotionRecord.entity.EmotionRecord;
4+
import org.dfbf.soundlink.domain.emotionRecord.entity.SpotifyMusic;
45
import org.dfbf.soundlink.domain.user.dto.response.EmotionRecordDto;
56
import org.dfbf.soundlink.domain.user.entity.User;
67
import org.dfbf.soundlink.global.comm.enums.Emotions;

src/main/java/org/dfbf/soundlink/domain/emotionRecord/repository/dsl/EmotionRecordRepositoryImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.dfbf.soundlink.domain.emotionRecord.entity.EmotionRecord;
1010
import org.dfbf.soundlink.domain.emotionRecord.entity.QEmotionRecord;
1111
import org.dfbf.soundlink.domain.emotionRecord.entity.QSpotifyMusic;
12+
import org.dfbf.soundlink.domain.emotionRecord.entity.SpotifyMusic;
1213
import org.dfbf.soundlink.domain.user.dto.response.EmotionRecordDto;
1314
import org.dfbf.soundlink.domain.user.entity.QUser;
1415
import org.dfbf.soundlink.domain.user.entity.User;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.dfbf.soundlink.domain.emotionRecord.repository.dsl;
2+
3+
import org.dfbf.soundlink.domain.emotionRecord.entity.SpotifyMusic;
4+
5+
import java.util.List;
6+
7+
public interface SpotifyMusicRepositoryCustom {
8+
List<SpotifyMusic> findListBySpotifyId(String spotifyId);
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.dfbf.soundlink.domain.emotionRecord.repository.dsl;
2+
3+
import com.querydsl.jpa.impl.JPAQueryFactory;
4+
import lombok.RequiredArgsConstructor;
5+
import org.dfbf.soundlink.domain.emotionRecord.entity.QSpotifyMusic;
6+
import org.dfbf.soundlink.domain.emotionRecord.entity.SpotifyMusic;
7+
import org.springframework.stereotype.Repository;
8+
9+
import java.util.*;
10+
11+
@Repository
12+
@RequiredArgsConstructor
13+
public class SpotifyMusicRepositoryImpl implements SpotifyMusicRepositoryCustom {
14+
15+
private final JPAQueryFactory jpaQueryFactory;
16+
17+
@Override
18+
public List<SpotifyMusic> findListBySpotifyId(String spotifyId) {
19+
return jpaQueryFactory
20+
.selectFrom(QSpotifyMusic.spotifyMusic)
21+
.where(QSpotifyMusic.spotifyMusic.spotifyId.eq(spotifyId))
22+
.fetch();
23+
}
24+
}

src/main/java/org/dfbf/soundlink/domain/user/service/UserService.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import jakarta.mail.MessagingException;
5+
import jakarta.persistence.OptimisticLockException;
56
import jakarta.servlet.http.HttpServletRequest;
67
import jakarta.servlet.http.HttpServletResponse;
78
import jakarta.transaction.Transactional;
89
import lombok.RequiredArgsConstructor;
910
import lombok.extern.slf4j.Slf4j;
1011
import org.dfbf.soundlink.domain.alert.service.AlertService;
1112
import org.dfbf.soundlink.domain.blocklist.repository.BlockListRepository;
13+
import org.dfbf.soundlink.domain.emotionRecord.dto.request.EmotionRecordUpdateRequestDTO;
1214
import org.dfbf.soundlink.domain.emotionRecord.entity.SpotifyMusic;
1315
import org.dfbf.soundlink.domain.emotionRecord.repository.EmotionRecordRepository;
1416
import org.dfbf.soundlink.domain.emotionRecord.repository.SpotifyMusicRepository;
@@ -29,11 +31,15 @@
2931
import org.springframework.beans.factory.annotation.Value;
3032
import org.springframework.data.redis.core.RedisTemplate;
3133
import org.springframework.http.ResponseCookie;
34+
import org.springframework.retry.annotation.Backoff;
35+
import org.springframework.retry.annotation.Recover;
36+
import org.springframework.retry.annotation.Retryable;
3237
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
3338
import org.springframework.stereotype.Service;
3439

3540
import javax.naming.AuthenticationException;
3641
import java.util.HashMap;
42+
import java.util.List;
3743
import java.util.Map;
3844
import java.util.concurrent.TimeUnit;
3945

@@ -95,6 +101,11 @@ public ResponseResult getUser(Long userId) {
95101

96102
// 회원정보 수정
97103
@Transactional
104+
@Retryable(
105+
retryFor = OptimisticLockException.class, // 낙관적 락 충돌 시 재시도
106+
maxAttempts = 3, // 최대 3번 재시도
107+
backoff = @Backoff(delay = 100) // 100ms(0.1초) 대기 후 재시도
108+
)
98109
public ResponseResult updateUser(Long userId, UserUpdateDto userUpdateDto) {
99110
/**
100111
* orElse -> 일단 함수는 실행, 그러나 값이 null이면 orElse의 값으로 대체 (함수O, 람다x)
@@ -107,14 +118,17 @@ public ResponseResult updateUser(Long userId, UserUpdateDto userUpdateDto) {
107118
String spotifyId = userUpdateDto.spotifyId().orElse(null);
108119

109120
if (spotifyId != null && !spotifyId.equals("-1")) {
110-
// SpotifyMusic 객체 찾기 (없으면 새로 생성 & 저장)
111-
SpotifyMusic spotifyMusic = spotifyMusicRepository.findBySpotifyId(spotifyId)
112-
.orElseGet(() -> {
113-
SpotifyMusic sm = new SpotifyMusic(userUpdateDto);
114-
spotifyMusicRepository.save(sm);
115-
return sm;
116-
});
117-
user.update(userUpdateDto, passwordEncoder, spotifyMusic);
121+
// SpotifyMusic 객체 찾기
122+
List<SpotifyMusic> spotifyMusicList = spotifyMusicRepository.findListBySpotifyId(spotifyId);
123+
124+
// (없으면 새로 생성 & 저장)
125+
if (spotifyMusicList.isEmpty()) {
126+
SpotifyMusic sm = new SpotifyMusic(userUpdateDto);
127+
spotifyMusicRepository.save(sm);
128+
user.update(userUpdateDto, passwordEncoder, sm);
129+
}
130+
131+
user.update(userUpdateDto, passwordEncoder, spotifyMusicList.get(0));
118132
} else {
119133
if("-1".equals(spotifyId)) { user.getProfileMusic().deleteSpotifyId(); }
120134
user.update(userUpdateDto, passwordEncoder);
@@ -131,6 +145,15 @@ public ResponseResult updateUser(Long userId, UserUpdateDto userUpdateDto) {
131145
}
132146
}
133147

148+
// 낙관적 락 재시도 실패 시 실행되는 메서드
149+
// Recover 어노테이션에 의해 실패 시, 자동 호출됨
150+
@Recover
151+
public ResponseResult recoverFromOptimisticLock(OptimisticLockException e, Long userId, UserUpdateDto userUpdateDto) {
152+
log.error("감정 기록 업데이트 중 동시성 충돌 발생. spotifyId: {}, error: {}", userUpdateDto.spotifyId(), e.getMessage());
153+
154+
return new ResponseResult(ErrorCode.CONCURRENCY_ERROR, e.getMessage());
155+
}
156+
134157
// 회원정보 삭제
135158
@Transactional
136159
public ResponseResult deleteUser(Long userId) {

0 commit comments

Comments
 (0)