From 0ed68c198f2116bbb81dc64ca61bc87f7f81d6e8 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sun, 29 Oct 2023 21:16:55 +0900 Subject: [PATCH 01/19] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=EC=85=89=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/shook/auth/config/AuthConfig.java | 2 ++ .../shook/auth/config/LocalAuthConfig.java | 23 ++++++++++++++++ .../auth/ui/interceptor/LocalInterceptor.java | 26 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java create mode 100644 backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java diff --git a/backend/src/main/java/shook/shook/auth/config/AuthConfig.java b/backend/src/main/java/shook/shook/auth/config/AuthConfig.java index 59f446a4..fdd674e6 100644 --- a/backend/src/main/java/shook/shook/auth/config/AuthConfig.java +++ b/backend/src/main/java/shook/shook/auth/config/AuthConfig.java @@ -2,6 +2,7 @@ import java.util.List; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -12,6 +13,7 @@ import shook.shook.auth.ui.interceptor.PathMethod; import shook.shook.auth.ui.interceptor.TokenInterceptor; +@Profile("!local") @Configuration public class AuthConfig implements WebMvcConfigurer { diff --git a/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java b/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java new file mode 100644 index 00000000..ce52100e --- /dev/null +++ b/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java @@ -0,0 +1,23 @@ +package shook.shook.auth.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import shook.shook.auth.ui.interceptor.LocalInterceptor; + +@Profile("local") +@Configuration +public class LocalAuthConfig implements WebMvcConfigurer { + + private final LocalInterceptor localInterceptor; + + public LocalAuthConfig(final LocalInterceptor localInterceptor) { + this.localInterceptor = localInterceptor; + } + + @Override + public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(localInterceptor); + } +} diff --git a/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java b/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java new file mode 100644 index 00000000..ecdc03bc --- /dev/null +++ b/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java @@ -0,0 +1,26 @@ +package shook.shook.auth.ui.interceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import shook.shook.auth.ui.AuthContext; + +@Profile("local") +@Component +public class LocalInterceptor implements HandlerInterceptor { + + private final AuthContext authContext; + + public LocalInterceptor(final AuthContext authContext) { + this.authContext = authContext; + } + + @Override + public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) + throws Exception { + authContext.setAuthenticatedMember(1L); + return true; + } +} From 64e0a8877d565ff4d3b010f29e561602cfb2e7be Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:42:27 +0900 Subject: [PATCH 02/19] =?UTF-8?q?refactor:=20TreeMap=20=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=85=B8=EB=9E=98=20=EC=BA=90=EC=8B=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/auth/config/LocalAuthConfig.java | 12 +- .../application/InMemorySongsScheduler.java | 2 +- .../killingpart/KillingPartLikeService.java | 14 +- .../shook/song/domain/InMemorySongs.java | 131 ++++++++++++++---- .../shook/shook/song/domain/KillingParts.java | 5 +- .../song/domain/killingpart/KillingPart.java | 12 ++ .../domain/killingpart/KillingPartLikes.java | 6 +- .../domain/repository/SongRepository.java | 7 + .../dto/SongWithKillingPartsAndLikes.java | 22 +++ backend/src/main/resources/application.yml | 8 +- .../song/application/SongServiceTest.java | 92 ++++++------ .../KillingPartLikeConcurrencyTest.java | 62 +++++---- .../KillingPartLikeServiceTest.java | 35 ++--- .../shook/song/domain/InMemorySongsTest.java | 6 +- 14 files changed, 284 insertions(+), 130 deletions(-) create mode 100644 backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java diff --git a/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java b/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java index ce52100e..03d43379 100644 --- a/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java +++ b/backend/src/main/java/shook/shook/auth/config/LocalAuthConfig.java @@ -1,18 +1,23 @@ package shook.shook.auth.config; +import java.util.List; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import shook.shook.auth.ui.argumentresolver.AuthArgumentResolver; import shook.shook.auth.ui.interceptor.LocalInterceptor; @Profile("local") @Configuration public class LocalAuthConfig implements WebMvcConfigurer { + private final AuthArgumentResolver authArgumentResolver; private final LocalInterceptor localInterceptor; - public LocalAuthConfig(final LocalInterceptor localInterceptor) { + public LocalAuthConfig(final AuthArgumentResolver authArgumentResolver, final LocalInterceptor localInterceptor) { + this.authArgumentResolver = authArgumentResolver; this.localInterceptor = localInterceptor; } @@ -20,4 +25,9 @@ public LocalAuthConfig(final LocalInterceptor localInterceptor) { public void addInterceptors(final InterceptorRegistry registry) { registry.addInterceptor(localInterceptor); } + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(authArgumentResolver); + } } diff --git a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java index f0f102ef..505bf50e 100644 --- a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java +++ b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java @@ -26,6 +26,6 @@ public void initialize() { @Scheduled(cron = "${schedules.in-memory-song.cron}") public void recreateCachedSong() { log.info("InMemorySongsScheduler worked"); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); } } diff --git a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java index 73cac9d3..4cad0876 100644 --- a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java +++ b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java @@ -8,6 +8,7 @@ import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; @@ -22,6 +23,7 @@ public class KillingPartLikeService { private final KillingPartRepository killingPartRepository; private final MemberRepository memberRepository; private final KillingPartLikeRepository likeRepository; + private final InMemorySongs inMemorySongs; @Transactional public void updateLikeStatus( @@ -53,9 +55,10 @@ private void create(final KillingPart killingPart, final Member member) { final KillingPartLike likeOnKillingPart = likeRepository.findByKillingPartAndMember(killingPart, member) .orElseGet(() -> createNewLike(killingPart, member)); - if (likeOnKillingPart.isDeleted()) { - likeRepository.pressLike(likeOnKillingPart.getId()); - killingPartRepository.increaseLikeCount(killingPart.getId()); + if (likeOnKillingPart.isDeleted()) { // 좋아요를 취소한 경우 +// likeRepository.pressLike(likeOnKillingPart.getId()); +// killingPartRepository.increaseLikeCount(killingPart.getId()); + inMemorySongs.pressLike(killingPart, likeOnKillingPart); } } @@ -68,8 +71,9 @@ private KillingPartLike createNewLike(final KillingPart killingPart, final Membe private void delete(final KillingPart killingPart, final Member member) { killingPart.findLikeByMember(member) .ifPresent(likeOnKillingPart -> { - likeRepository.cancelLike(likeOnKillingPart.getId()); - killingPartRepository.decreaseLikeCount(killingPart.getId()); +// likeRepository.cancelLike(likeOnKillingPart.getId()); +// killingPartRepository.decreaseLikeCount(killingPart.getId()); + inMemorySongs.cancelLike(killingPart, likeOnKillingPart); }); } } diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 6adecdff..3e051093 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -1,41 +1,51 @@ package shook.shook.song.domain; +import jakarta.persistence.EntityManager; import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import shook.shook.song.domain.killingpart.KillingPart; +import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.exception.SongException; +import shook.shook.song.exception.killingpart.KillingPartException; +@RequiredArgsConstructor @Repository public class InMemorySongs { - private Map songsSortedInLikeCountById = new LinkedHashMap<>(); + private Map songsSortedInLikeCountById; + private Map songLikeCountById = new HashMap<>(); + + private final EntityManager entityManager; public void recreate(final List songs) { songsSortedInLikeCountById = getSortedSong(songs); + songLikeCountById = songsSortedInLikeCountById.keySet().stream() + .collect(Collectors.toMap(SongKey::getId, key -> key)); + + songsSortedInLikeCountById.values().stream() + .peek(entityManager::detach) + .flatMap(song -> song.getKillingParts().stream()) + .peek(entityManager::detach) + .flatMap(killingPart -> killingPart.getKillingPartLikes().stream()) + .forEach(entityManager::detach); } - private static Map getSortedSong(final List songs) { - songs.sort(Comparator.comparing( - Song::getTotalLikeCount, - Comparator.reverseOrder() - ).thenComparing(Song::getId, Comparator.reverseOrder())); - + private static Map getSortedSong(final List songs) { return songs.stream() - .collect(Collectors.toMap( - Song::getId, - song -> song, - (prev, update) -> update, - LinkedHashMap::new - )); + .collect(Collectors.toMap(song -> new SongKey(song.getId(), new AtomicInteger(song.getTotalLikeCount())), + song -> song, (prev, update) -> update, TreeMap::new)); } public List getSongs() { - return songsSortedInLikeCountById.values().stream() - .toList(); + return songsSortedInLikeCountById.values().stream().toList(); } public List getSongs(final int limit) { @@ -45,9 +55,7 @@ public List getSongs(final int limit) { } public List getSortedSongsByGenre(final Genre genre) { - return songsSortedInLikeCountById.values().stream() - .filter(song -> song.getGenre() == genre) - .toList(); + return songsSortedInLikeCountById.values().stream().filter(song -> song.getGenre() == genre).toList(); } public List getSortedSongsByGenre(final Genre genre, final int limit) { @@ -57,12 +65,11 @@ public List getSortedSongsByGenre(final Genre genre, final int limit) { } public Song getSongById(final Long id) { - if (songsSortedInLikeCountById.containsKey(id)) { - return songsSortedInLikeCountById.get(id); + final SongKey key = songLikeCountById.get(id); + if (songsSortedInLikeCountById.containsKey(key)) { + return songsSortedInLikeCountById.get(key); } - throw new SongException.SongNotExistException( - Map.of("song id", String.valueOf(id)) - ); + throw new SongException.SongNotExistException(Map.of("song id", String.valueOf(id))); } public List getPrevLikedSongByGenre(final Song currentSong, final Genre genre, final int prevSongCount) { @@ -81,21 +88,24 @@ public List getNextLikedSongByGenre(final Song currentSong, final Genre ge } return songsWithGenre.subList(Math.min(currentSongIndex + 1, songsWithGenre.size() - 1), - Math.min(songsWithGenre.size(), currentSongIndex + nextSongCount + 1)); + Math.min(songsWithGenre.size(), currentSongIndex + nextSongCount + 1)); } public List getPrevLikedSongs(final Song currentSong, final int prevSongCount) { final List songIds = songsSortedInLikeCountById.keySet().stream() + .map(SongKey::getId) .toList(); final int currentSongIndex = songIds.indexOf(currentSong.getId()); return songIds.subList(Math.max(0, currentSongIndex - prevSongCount), currentSongIndex).stream() + .map(songLikeCountById::get) .map(songsSortedInLikeCountById::get) .toList(); } public List getNextLikedSongs(final Song currentSong, final int nextSongCount) { final List songIds = songsSortedInLikeCountById.keySet().stream() + .map(SongKey::getId) .toList(); final int currentSongIndex = songIds.indexOf(currentSong.getId()); @@ -104,8 +114,75 @@ public List getNextLikedSongs(final Song currentSong, final int nextSongCo } return songIds.subList(Math.min(currentSongIndex + 1, songIds.size() - 1), - Math.min(songIds.size(), currentSongIndex + nextSongCount + 1)).stream() + Math.min(songIds.size(), currentSongIndex + nextSongCount + 1)).stream() + .map(songLikeCountById::get) .map(songsSortedInLikeCountById::get) .toList(); } + + public void pressLike(final KillingPart killingPart, final KillingPartLike likeOnKillingPart) { + final SongKey songKey = songLikeCountById.remove(killingPart.getSong().getId()); + final Song song = songsSortedInLikeCountById.remove(songKey); + songKey.likeCount.incrementAndGet(); + songLikeCountById.put(song.getId(), songKey); + songsSortedInLikeCountById.put(songKey, song); + final KillingPart killingPartById = findKillingPart(killingPart, song); + killingPartById.like(likeOnKillingPart); + } + + private static KillingPart findKillingPart(final KillingPart killingPart, final Song song) { + return song.getKillingParts().stream().filter(kp -> kp.equals(killingPart)).findAny().orElseThrow( + () -> new KillingPartException.PartNotExistException( + Map.of("killing part id", String.valueOf(killingPart.getId())))); + } + + public void cancelLike(final KillingPart killingPart, final KillingPartLike unlikeOnKillingPart) { + final SongKey songKey = songLikeCountById.remove(killingPart.getSong().getId()); + final Song song = songsSortedInLikeCountById.remove(songKey); + songKey.likeCount.decrementAndGet(); + songLikeCountById.put(song.getId(), songKey); + songsSortedInLikeCountById.put(songKey, song); + final KillingPart killingPartById = findKillingPart(killingPart, song); + killingPartById.unlike(unlikeOnKillingPart); + } + + private static class SongKey implements Comparable { + + private final Long id; + private final AtomicInteger likeCount; + + public SongKey(final Long id, final AtomicInteger likeCount) { + this.id = id; + this.likeCount = likeCount; + } + + public Long getId() { + return id; + } + + @Override + public int compareTo(final SongKey o) { + if (o.likeCount.get() == this.likeCount.get()) { + return o.id.compareTo(this.id); + } + return o.likeCount.get() - this.likeCount.get(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final SongKey songKey = (SongKey) o; + return Objects.equals(id, songKey.id) && Objects.equals(likeCount.get(), songKey.likeCount.get()); + } + + @Override + public int hashCode() { + return Objects.hash(id, likeCount); + } + } } diff --git a/backend/src/main/java/shook/shook/song/domain/KillingParts.java b/backend/src/main/java/shook/shook/song/domain/KillingParts.java index 5300cdbd..1a4f8166 100644 --- a/backend/src/main/java/shook/shook/song/domain/KillingParts.java +++ b/backend/src/main/java/shook/shook/song/domain/KillingParts.java @@ -58,12 +58,13 @@ public List getKillingParts() { public List getKillingPartsSortedByLikeCount() { return killingParts.stream() .sorted(Comparator.comparing(KillingPart::getLikeCount, Comparator.reverseOrder()) - .thenComparing(KillingPart::getStartSecond)) + .thenComparing(KillingPart::getStartSecond)) .toList(); } public int getKillingPartsTotalLikeCount() { return killingParts.stream() - .reduce(0, (sum, killingPart) -> sum + killingPart.getLikeCount(), Integer::sum); + .mapToInt(killingPart -> killingPart.getAtomicLikeCount().get()) + .reduce(0, Integer::sum); } } diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java index 0114edf8..d665a88e 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java @@ -10,6 +10,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.PostLoad; import jakarta.persistence.PrePersist; import jakarta.persistence.Table; import java.time.LocalDateTime; @@ -19,6 +20,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -63,6 +65,8 @@ public class KillingPart { @Column(nullable = false) private int likeCount = 0; + private transient AtomicInteger atomicLikeCount; + @Column(nullable = false, updatable = false) private LocalDateTime createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); @@ -83,6 +87,7 @@ private KillingPart( this.length = new PartLength(length); this.song = song; this.likeCount = likeCount; + this.atomicLikeCount = new AtomicInteger(likeCount); } private KillingPart(final int startSecond, final int length) { @@ -102,6 +107,11 @@ public static KillingPart forSave(final int startSecond, final int length) { return new KillingPart(startSecond, length); } + @PostLoad + private void postLoad() { + this.atomicLikeCount = new AtomicInteger(likeCount); + } + public void addComment(final KillingPartComment comment) { if (comment.isBelongToOtherKillingPart(this)) { throw new KillingPartCommentException.CommentForOtherPartException( @@ -119,6 +129,7 @@ public void like(final KillingPartLike likeToAdd) { final boolean isLikeCreated = killingPartLikes.addLike(likeToAdd); if (isLikeCreated) { this.likeCount++; + atomicLikeCount.incrementAndGet(); } } @@ -141,6 +152,7 @@ public void unlike(final KillingPartLike likeToDelete) { final boolean isLikeDeleted = killingPartLikes.deleteLike(likeToDelete); if (isLikeDeleted) { this.likeCount--; + atomicLikeCount.decrementAndGet(); } } diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java index 91df7f94..3aef76b6 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPartLikes.java @@ -2,9 +2,9 @@ import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,7 +18,7 @@ public class KillingPartLikes { @OneToMany(mappedBy = "killingPart") @Where(clause = "is_deleted = false") - private List likes = new ArrayList<>(); + private Set likes = new HashSet<>(); public boolean addLike(final KillingPartLike like) { if (like.isDeleted()) { diff --git a/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java b/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java index d9321bd8..542988ec 100644 --- a/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java +++ b/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java @@ -25,6 +25,13 @@ public interface SongRepository extends JpaRepository { + "GROUP BY s.id, kp.id") List findAllWithKillingParts(); + @Query("SELECT s AS song " + + "FROM Song s " + + "LEFT JOIN FETCH s.killingParts.killingParts kp " + + "LEFT JOIN FETCH kp.killingPartLikes.likes kpl " + + "GROUP BY s.id, kp.id, kpl.id") + List findAllWithKillingPartsAndLikes(); + @Query("SELECT s FROM Song s " + "LEFT JOIN s.killingParts.killingParts kp " + "GROUP BY s.id " diff --git a/backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java b/backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java new file mode 100644 index 00000000..465e57f0 --- /dev/null +++ b/backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java @@ -0,0 +1,22 @@ +package shook.shook.song.domain.repository.dto; + +import java.util.List; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.killingpart.KillingPart; +import shook.shook.song.domain.killingpart.KillingPartLike; + +public interface SongWithKillingPartsAndLikes { + + Song getSong(); + + default List getKillingPart() { + return getSong().getKillingParts(); + } + + default List getKillingPartLikes() { + return getKillingPart().stream() + .map(KillingPart::getKillingPartLikes) + .flatMap(List::stream) + .toList(); + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index c3334135..b27581e8 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -12,8 +12,10 @@ spring: on-profile: local datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:shook;MODE=MySQL + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/shook + username: root + password: root h2: console: @@ -21,7 +23,7 @@ spring: sql: init: - mode: always + mode: never schema-locations: classpath:schema.sql jpa: diff --git a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java index 4cd3d876..e3a5064c 100644 --- a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import jakarta.persistence.EntityManager; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -18,8 +19,8 @@ import shook.shook.member_part.domain.MemberPart; import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.KillingPartRegisterRequest; -import shook.shook.song.application.dto.RecentSongCarouselResponse; import shook.shook.song.application.dto.MemberPartResponse; +import shook.shook.song.application.dto.RecentSongCarouselResponse; import shook.shook.song.application.dto.SongResponse; import shook.shook.song.application.dto.SongSwipeResponse; import shook.shook.song.application.dto.SongWithKillingPartsRegisterRequest; @@ -56,7 +57,10 @@ class SongServiceTest extends UsingJpaTest { @Autowired private ArtistRepository artistRepository; - private final InMemorySongs inMemorySongs = new InMemorySongs(); + @Autowired + private EntityManager entityManager; + + private final InMemorySongs inMemorySongs = new InMemorySongs(entityManager); private SongService songService; @@ -125,7 +129,7 @@ void findById_exist_login_member() { saveAndClearEntityManager(); final SongSwipeResponse response = songService.findSongByIdForFirstSwipe(song.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); //then assertAll( @@ -133,19 +137,19 @@ void findById_exist_login_member() { () -> assertThat(response.getNextSongs()).isEmpty(), () -> assertThat(response.getCurrentSong().getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getCurrentSong().getMemberPart().getId()).isNotNull() @@ -168,7 +172,7 @@ void findById_exist_not_login_member() { saveAndClearEntityManager(); final SongSwipeResponse response = songService.findSongByIdForFirstSwipe(song.getId(), - new MemberInfo(0L, Authority.ANONYMOUS)); + new MemberInfo(0L, Authority.ANONYMOUS)); //then assertAll( @@ -176,19 +180,19 @@ void findById_exist_not_login_member() { () -> assertThat(response.getNextSongs()).isEmpty(), () -> assertThat(response.getCurrentSong().getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) .hasFieldOrPropertyWithValue("likeStatus", false), () -> assertThat(response.getCurrentSong().getMemberPart()).isNull() @@ -204,9 +208,9 @@ void findById_notExist() { //when //then assertThatThrownBy(() -> songService.findSongByIdForFirstSwipe( - 0L, - new MemberInfo(member.getId(), Authority.MEMBER) - ) + 0L, + new MemberInfo(member.getId(), Authority.MEMBER) + ) ).isInstanceOf(SongException.SongNotExistException.class); } @@ -317,7 +321,7 @@ void firstFindByMember() { // when final SongSwipeResponse result = songService.findSongByIdForFirstSwipe(fifthSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertAll( @@ -325,20 +329,20 @@ void firstFindByMember() { () -> assertThat(result.getPrevSongs()).hasSize(2), () -> assertThat(result.getNextSongs()).hasSize(2), () -> assertThat(result.getPrevSongs().stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(4L, 3L)), + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(4L, 3L)), () -> assertThat(result.getNextSongs().stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 1L)), + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 1L)), () -> assertThat(result.getCurrentSong().getMemberPart()).isNull(), () -> assertThat(result.getPrevSongs().stream() - .map(songResponse -> songResponse.getMemberPart().getId()) - .toList()) + .map(songResponse -> songResponse.getMemberPart().getId()) + .toList()) .usingRecursiveComparison() .isEqualTo(List.of(4L, 3L)), () -> assertThat(result.getNextSongs().stream() - .map(songResponse -> songResponse.getMemberPart().getId()) - .toList()) + .map(songResponse -> songResponse.getMemberPart().getId()) + .toList()) .usingRecursiveComparison() .isEqualTo(List.of(2L, 1L)) ); @@ -355,15 +359,15 @@ void firstFindByAnonymous() { // then assertThatThrownBy( () -> songService.findSongByIdForFirstSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); assertThatThrownBy( () -> songService.findSongByIdForBeforeSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); assertThatThrownBy( () -> songService.findSongByIdForAfterSwipe(notExistSongId, - new MemberInfo(member.getId(), Authority.MEMBER))) + new MemberInfo(member.getId(), Authority.MEMBER))) .isInstanceOf(SongException.SongNotExistException.class); } @@ -399,16 +403,16 @@ void findSongByIdForBeforeSwipe() { // when final List beforeResponses = songService.findSongByIdForBeforeSwipe(standardSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertThat(beforeResponses.stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); assertThat(beforeResponses.stream() - .map(SongResponse::getMemberPart) - .map(MemberPartResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); + .map(SongResponse::getMemberPart) + .map(MemberPartResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(2L, 4L, 1L, 5L)); } @DisplayName("이후 노래를 1. 좋아요 순 내림차순, 2. id 내림차순으로 조회한다.") @@ -443,16 +447,16 @@ void findSongByIdForAfterSwipe() { // when final List afterResponses = songService.findSongByIdForAfterSwipe(standardSong.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertThat(afterResponses.stream() - .map(SongResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); + .map(SongResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); assertThat(afterResponses.stream() - .map(SongResponse::getMemberPart) - .map(MemberPartResponse::getId) - .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); + .map(SongResponse::getMemberPart) + .map(MemberPartResponse::getId) + .toList()).usingRecursiveComparison().isEqualTo(List.of(1L, 5L, 3L)); } } @@ -491,7 +495,7 @@ void findSongsByGenre() { assertAll( () -> assertThat(response).hasSize(5), () -> assertThat(response.stream() - .map(HighLikedSongResponse::getId).toList()) + .map(HighLikedSongResponse::getId).toList()) .containsExactly(2L, 1L, 3L, 5L, 4L) ); } @@ -510,7 +514,7 @@ void findSongById() { // when final SongResponse response = songService.findSongById(song.getId(), - new MemberInfo(member.getId(), Authority.MEMBER)); + new MemberInfo(member.getId(), Authority.MEMBER)); // then assertAll( @@ -521,19 +525,19 @@ void findSongById() { () -> assertThat(response.getKillingParts()).hasSize(3), () -> assertThat(response.getKillingParts().get(0)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(0).getId()) + song.getLikeCountSortedKillingParts().get(0).getId()) .hasFieldOrPropertyWithValue("rank", 1) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getKillingParts().get(1)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(1).getId()) + song.getLikeCountSortedKillingParts().get(1).getId()) .hasFieldOrPropertyWithValue("rank", 2) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getKillingParts().get(2)) .hasFieldOrPropertyWithValue("id", - song.getLikeCountSortedKillingParts().get(2).getId()) + song.getLikeCountSortedKillingParts().get(2).getId()) .hasFieldOrPropertyWithValue("rank", 3) .hasFieldOrPropertyWithValue("likeStatus", true), () -> assertThat(response.getMemberPart().getId()).isNotNull() @@ -559,8 +563,8 @@ void findRecentRegisteredSongsForCarousel() { // then assertThat(songs.stream() - .map(RecentSongCarouselResponse::getId) - .toList()) + .map(RecentSongCarouselResponse::getId) + .toList()) .containsExactly(7L, 6L, 5L, 4L, 3L); } } diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java index 2070e9e4..c7002a77 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java @@ -18,6 +18,7 @@ import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; @@ -41,6 +42,9 @@ class KillingPartLikeConcurrencyTest { @Autowired private PlatformTransactionManager transactionManager; + @Autowired + private InMemorySongs inMemorySongs; + private KillingPartLikeService likeService; private TransactionTemplate transactionTemplate; @@ -48,7 +52,8 @@ class KillingPartLikeConcurrencyTest { void setUp() { SAVED_KILLING_PART = killingPartRepository.findById(1L).get(); SAVED_MEMBER = memberRepository.findById(1L).get(); - likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository); + likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository, + inMemorySongs); transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); } @@ -67,18 +72,20 @@ void likeByMultiplePeople() throws InterruptedException { final KillingPartLikeRequest request = new KillingPartLikeRequest(true); executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), request); - latch.countDown(); - return null; - })) + transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), + request); + latch.countDown(); + return null; + })) ); executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), second.getId(), request); - latch.countDown(); - return null; - })) + transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), second.getId(), + request); + latch.countDown(); + return null; + })) ); latch.await(); Thread.sleep(1000); @@ -103,25 +110,28 @@ void likeByOnePersonMultipleTimes() throws InterruptedException { final KillingPartLikeRequest unlikeRequest = new KillingPartLikeRequest(false); executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); - latch.countDown(); - return null; - })) + transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), + likeRequest); + latch.countDown(); + return null; + })) ); executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), unlikeRequest); - latch.countDown(); - return null; - })) + transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), + unlikeRequest); + latch.countDown(); + return null; + })) ); executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); - latch.countDown(); - return null; - })) + transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), + likeRequest); + latch.countDown(); + return null; + })) ); latch.await(); diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java index 3d79a5b1..5a06fbe5 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java @@ -14,6 +14,7 @@ import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; @@ -45,7 +46,7 @@ void setUp() { SAVED_KILLING_PART = killingPartRepository.findById(1L).get(); SAVED_MEMBER = memberRepository.findById(1L).get(); likeService = new KillingPartLikeService(killingPartRepository, memberRepository, - killingPartLikeRepository); + killingPartLikeRepository, new InMemorySongs(entityManager)); } @DisplayName("킬링파트 좋아요를 누른다.") @@ -58,7 +59,7 @@ void create_newLike() { // given // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // then @@ -81,14 +82,14 @@ void create_newLike() { void create_updateLike_exist() { // given likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // then @@ -111,12 +112,12 @@ void create_updateLike_exist() { void create_noAction() { // given likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // then @@ -141,7 +142,7 @@ void create_KillingPartException() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(UNSAVED_KILLING_PART_ID, SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true))) + new KillingPartLikeRequest(true))) .isInstanceOf(KillingPartException.PartNotExistException.class); } @@ -152,7 +153,7 @@ void create_memberNotExist() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), UNSAVED_MEMBER_ID, - new KillingPartLikeRequest(true))) + new KillingPartLikeRequest(true))) .isInstanceOf(MemberException.MemberNotExistException.class); } } @@ -167,7 +168,7 @@ void delete_noAction() { // given // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // then @@ -187,14 +188,14 @@ void delete_noAction() { void delete_alreadyDeleted_noAction() { // given likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // then @@ -217,12 +218,12 @@ void delete_alreadyDeleted_noAction() { void create_noAction() { // given likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); saveAndClearEntityManager(); // when likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), - new KillingPartLikeRequest(false)); + new KillingPartLikeRequest(false)); saveAndClearEntityManager(); // then @@ -247,7 +248,7 @@ void create_KillingPartException() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(UNSAVED_KILLING_PART_ID, SAVED_MEMBER.getId(), - new KillingPartLikeRequest(true))) + new KillingPartLikeRequest(true))) .isInstanceOf(KillingPartException.PartNotExistException.class); } @@ -258,7 +259,7 @@ void create_memberNotExist() { // when, then assertThatThrownBy( () -> likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), UNSAVED_MEMBER_ID, - new KillingPartLikeRequest(false))) + new KillingPartLikeRequest(false))) .isInstanceOf(MemberException.MemberNotExistException.class); } } diff --git a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java index 872023c8..25273817 100644 --- a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java +++ b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import jakarta.persistence.EntityManager; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -29,10 +30,13 @@ class InMemorySongsTest extends UsingJpaTest { @Autowired private MemberRepository memberRepository; + @Autowired + private EntityManager entityManager; + @BeforeEach void setUp() { MEMBER = memberRepository.findById(1L).get(); - inMemorySongs = new InMemorySongs(); + inMemorySongs = new InMemorySongs(entityManager); } @DisplayName("InMemorySong 을 1.좋아요 순, 2. id 순으로 정렬된 노래로 초기화한다.") From 44f67c59ef27f24d08a519f11a7e4ce697a18742 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:22:29 +0900 Subject: [PATCH 03/19] =?UTF-8?q?refactor:=20=EC=82=BD=EC=9E=85=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=EB=A1=9C=20=EC=A0=95=EB=A0=AC=20=EC=86=8D?= =?UTF-8?q?=EB=8F=84=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingpart/KillingPartLikeService.java | 10 +- .../shook/song/domain/InMemorySongs.java | 135 +++++++----------- .../song/domain/killingpart/KillingPart.java | 8 +- .../src/main/resources/application-test.yml | 2 +- 4 files changed, 59 insertions(+), 96 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java index 4cad0876..dadc3177 100644 --- a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java +++ b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java @@ -55,10 +55,8 @@ private void create(final KillingPart killingPart, final Member member) { final KillingPartLike likeOnKillingPart = likeRepository.findByKillingPartAndMember(killingPart, member) .orElseGet(() -> createNewLike(killingPart, member)); - if (likeOnKillingPart.isDeleted()) { // 좋아요를 취소한 경우 -// likeRepository.pressLike(likeOnKillingPart.getId()); -// killingPartRepository.increaseLikeCount(killingPart.getId()); - inMemorySongs.pressLike(killingPart, likeOnKillingPart); + if (likeOnKillingPart.isDeleted()) { + inMemorySongs.like(killingPart, likeOnKillingPart); } } @@ -71,9 +69,7 @@ private KillingPartLike createNewLike(final KillingPart killingPart, final Membe private void delete(final KillingPart killingPart, final Member member) { killingPart.findLikeByMember(member) .ifPresent(likeOnKillingPart -> { -// likeRepository.cancelLike(likeOnKillingPart.getId()); -// killingPartRepository.decreaseLikeCount(killingPart.getId()); - inMemorySongs.cancelLike(killingPart, likeOnKillingPart); + inMemorySongs.unlike(killingPart, likeOnKillingPart); }); } } diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 3e051093..6f5b5c6c 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -1,13 +1,12 @@ package shook.shook.song.domain; import jakarta.persistence.EntityManager; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -20,15 +19,19 @@ @Repository public class InMemorySongs { - private Map songsSortedInLikeCountById; - private Map songLikeCountById = new HashMap<>(); + private static final Comparator COMPARATOR = Comparator.comparing(Song::getTotalLikeCount, + Comparator.reverseOrder()) + .thenComparing(Song::getId, Comparator.reverseOrder()); + private Map songsSortedInLikeCountById; + private List sortedIds; private final EntityManager entityManager; public void recreate(final List songs) { songsSortedInLikeCountById = getSortedSong(songs); - songLikeCountById = songsSortedInLikeCountById.keySet().stream() - .collect(Collectors.toMap(SongKey::getId, key -> key)); + sortedIds = new ArrayList<>(songsSortedInLikeCountById.keySet().stream() + .sorted(Comparator.comparing(songsSortedInLikeCountById::get, COMPARATOR)) + .toList()); songsSortedInLikeCountById.values().stream() .peek(entityManager::detach) @@ -38,10 +41,9 @@ public void recreate(final List songs) { .forEach(entityManager::detach); } - private static Map getSortedSong(final List songs) { + private static Map getSortedSong(final List songs) { return songs.stream() - .collect(Collectors.toMap(song -> new SongKey(song.getId(), new AtomicInteger(song.getTotalLikeCount())), - song -> song, (prev, update) -> update, TreeMap::new)); + .collect(Collectors.toMap(Song::getId, song -> song, (prev, update) -> update, HashMap::new)); } public List getSongs() { @@ -49,13 +51,18 @@ public List getSongs() { } public List getSongs(final int limit) { - final List songs = getSongs(); + final List topSongIds = this.sortedIds.subList(0, Math.min(limit, this.sortedIds.size())); - return songs.subList(0, Math.min(limit, songs.size())); + return topSongIds.stream() + .map(songsSortedInLikeCountById::get) + .toList(); } public List getSortedSongsByGenre(final Genre genre) { - return songsSortedInLikeCountById.values().stream().filter(song -> song.getGenre() == genre).toList(); + return sortedIds.stream() + .map(songsSortedInLikeCountById::get) + .filter(song -> song.getGenre() == genre) + .toList(); } public List getSortedSongsByGenre(final Genre genre, final int limit) { @@ -65,9 +72,8 @@ public List getSortedSongsByGenre(final Genre genre, final int limit) { } public Song getSongById(final Long id) { - final SongKey key = songLikeCountById.get(id); - if (songsSortedInLikeCountById.containsKey(key)) { - return songsSortedInLikeCountById.get(key); + if (songsSortedInLikeCountById.containsKey(id)) { + return songsSortedInLikeCountById.get(id); } throw new SongException.SongNotExistException(Map.of("song id", String.valueOf(id))); } @@ -92,97 +98,54 @@ public List getNextLikedSongByGenre(final Song currentSong, final Genre ge } public List getPrevLikedSongs(final Song currentSong, final int prevSongCount) { - final List songIds = songsSortedInLikeCountById.keySet().stream() - .map(SongKey::getId) - .toList(); - final int currentSongIndex = songIds.indexOf(currentSong.getId()); + final int currentSongIndex = sortedIds.indexOf(currentSong.getId()); - return songIds.subList(Math.max(0, currentSongIndex - prevSongCount), currentSongIndex).stream() - .map(songLikeCountById::get) + return sortedIds.subList(Math.max(0, currentSongIndex - prevSongCount), currentSongIndex).stream() .map(songsSortedInLikeCountById::get) .toList(); } public List getNextLikedSongs(final Song currentSong, final int nextSongCount) { - final List songIds = songsSortedInLikeCountById.keySet().stream() - .map(SongKey::getId) - .toList(); - final int currentSongIndex = songIds.indexOf(currentSong.getId()); + final int currentSongIndex = sortedIds.indexOf(currentSong.getId()); - if (currentSongIndex == songIds.size() - 1) { + if (currentSongIndex == sortedIds.size() - 1) { return Collections.emptyList(); } - return songIds.subList(Math.min(currentSongIndex + 1, songIds.size() - 1), - Math.min(songIds.size(), currentSongIndex + nextSongCount + 1)).stream() - .map(songLikeCountById::get) + return sortedIds.subList(Math.min(currentSongIndex + 1, sortedIds.size() - 1), + Math.min(sortedIds.size(), currentSongIndex + nextSongCount + 1)).stream() .map(songsSortedInLikeCountById::get) .toList(); } - public void pressLike(final KillingPart killingPart, final KillingPartLike likeOnKillingPart) { - final SongKey songKey = songLikeCountById.remove(killingPart.getSong().getId()); - final Song song = songsSortedInLikeCountById.remove(songKey); - songKey.likeCount.incrementAndGet(); - songLikeCountById.put(song.getId(), songKey); - songsSortedInLikeCountById.put(songKey, song); + public void like(final KillingPart killingPart, final KillingPartLike likeOnKillingPart) { + final Song song = songsSortedInLikeCountById.get(killingPart.getSong().getId()); final KillingPart killingPartById = findKillingPart(killingPart, song); - killingPartById.like(likeOnKillingPart); + final boolean updated = killingPartById.like(likeOnKillingPart); + if (updated) { + sortSongIds(); + } } - private static KillingPart findKillingPart(final KillingPart killingPart, final Song song) { - return song.getKillingParts().stream().filter(kp -> kp.equals(killingPart)).findAny().orElseThrow( - () -> new KillingPartException.PartNotExistException( - Map.of("killing part id", String.valueOf(killingPart.getId())))); + private void sortSongIds() { + sortedIds.sort(Comparator.comparing(songsSortedInLikeCountById::get, COMPARATOR)); } - public void cancelLike(final KillingPart killingPart, final KillingPartLike unlikeOnKillingPart) { - final SongKey songKey = songLikeCountById.remove(killingPart.getSong().getId()); - final Song song = songsSortedInLikeCountById.remove(songKey); - songKey.likeCount.decrementAndGet(); - songLikeCountById.put(song.getId(), songKey); - songsSortedInLikeCountById.put(songKey, song); - final KillingPart killingPartById = findKillingPart(killingPart, song); - killingPartById.unlike(unlikeOnKillingPart); + private static KillingPart findKillingPart(final KillingPart killingPart, final Song song) { + return song.getKillingParts().stream() + .filter(kp -> kp.equals(killingPart)) + .findAny() + .orElseThrow( + () -> new KillingPartException.PartNotExistException( + Map.of("killing part id", String.valueOf(killingPart.getId())))); } - private static class SongKey implements Comparable { - - private final Long id; - private final AtomicInteger likeCount; - - public SongKey(final Long id, final AtomicInteger likeCount) { - this.id = id; - this.likeCount = likeCount; - } - - public Long getId() { - return id; - } - - @Override - public int compareTo(final SongKey o) { - if (o.likeCount.get() == this.likeCount.get()) { - return o.id.compareTo(this.id); - } - return o.likeCount.get() - this.likeCount.get(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final SongKey songKey = (SongKey) o; - return Objects.equals(id, songKey.id) && Objects.equals(likeCount.get(), songKey.likeCount.get()); - } - - @Override - public int hashCode() { - return Objects.hash(id, likeCount); + public void unlike(final KillingPart killingPart, final KillingPartLike unlikeOnKillingPart) { + final Song song = songsSortedInLikeCountById.get(killingPart.getSong().getId()); + final KillingPart killingPartById = findKillingPart(killingPart, song); + final boolean updated = killingPartById.unlike(unlikeOnKillingPart); + if (updated) { + sortSongIds(); } } } diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java index d665a88e..9ccc0c1a 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java @@ -124,13 +124,15 @@ public void addComment(final KillingPartComment comment) { comments.addComment(comment); } - public void like(final KillingPartLike likeToAdd) { + public boolean like(final KillingPartLike likeToAdd) { validateLikeUpdate(likeToAdd); final boolean isLikeCreated = killingPartLikes.addLike(likeToAdd); if (isLikeCreated) { this.likeCount++; atomicLikeCount.incrementAndGet(); + return true; } + return false; } private void validateLikeUpdate(final KillingPartLike like) { @@ -147,13 +149,15 @@ private void validateLikeUpdate(final KillingPartLike like) { } } - public void unlike(final KillingPartLike likeToDelete) { + public boolean unlike(final KillingPartLike likeToDelete) { validateLikeUpdate(likeToDelete); final boolean isLikeDeleted = killingPartLikes.deleteLike(likeToDelete); if (isLikeDeleted) { this.likeCount--; atomicLikeCount.decrementAndGet(); + return true; } + return false; } public Optional findLikeByMember(final Member member) { diff --git a/backend/src/main/resources/application-test.yml b/backend/src/main/resources/application-test.yml index fc22e8b1..baa73d9c 100644 --- a/backend/src/main/resources/application-test.yml +++ b/backend/src/main/resources/application-test.yml @@ -50,4 +50,4 @@ schedules: in-memory-token: cron: "0/1 * * * * *" in-memory-song: - cron: "0 0/1 * * * *" # 1분 + cron: "0 0/5 * * * *" # 1분 From 21f79764f446f56f7aff4f729382374049ff6858 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:27:44 +0900 Subject: [PATCH 04/19] =?UTF-8?q?refactor:=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=20=EC=8B=9C=20=ED=86=A0=ED=81=B0=EC=9D=B4=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20Authorization=EC=9D=98=20memberId=20?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/shook/auth/ui/interceptor/LocalInterceptor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java b/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java index ecdc03bc..4f70aed5 100644 --- a/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java +++ b/backend/src/main/java/shook/shook/auth/ui/interceptor/LocalInterceptor.java @@ -20,7 +20,9 @@ public LocalInterceptor(final AuthContext authContext) { @Override public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception { - authContext.setAuthenticatedMember(1L); + final long memberId = Long.parseLong(request.getHeader("Authorization")); + authContext.setAuthenticatedMember(memberId); + return true; } } From 1fa1b45a5edd553172a25fd14cae3262c262e194 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:30:46 +0900 Subject: [PATCH 05/19] =?UTF-8?q?config:=20=EB=A1=9C=EC=BB=AC=20config=20?= =?UTF-8?q?=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/application.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index b27581e8..c3334135 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -12,10 +12,8 @@ spring: on-profile: local datasource: - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/shook - username: root - password: root + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:shook;MODE=MySQL h2: console: @@ -23,7 +21,7 @@ spring: sql: init: - mode: never + mode: always schema-locations: classpath:schema.sql jpa: From d0d8fa2c359a605fbe42cf091de051af00bbaec0 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:41:57 +0900 Subject: [PATCH 06/19] =?UTF-8?q?fix:=20=EC=82=BD=EC=9E=85=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/song/domain/InMemorySongs.java | 101 ++++++++++++++++-- .../song/application/SongServiceTest.java | 28 +++-- .../KillingPartLikeConcurrencyTest.java | 14 ++- .../KillingPartLikeServiceTest.java | 36 +++++-- .../shook/song/domain/InMemorySongsTest.java | 16 +-- .../killingpart/KillingPartLikesTest.java | 4 +- .../song/ui/HighLikedSongControllerTest.java | 24 ++--- .../shook/song/ui/SongControllerTest.java | 2 +- .../song/ui/SongSwipeControllerTest.java | 16 ++- 9 files changed, 171 insertions(+), 70 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 6f5b5c6c..0bb8ce9d 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -19,11 +19,11 @@ @Repository public class InMemorySongs { - private static final Comparator COMPARATOR = Comparator.comparing(Song::getTotalLikeCount, - Comparator.reverseOrder()) - .thenComparing(Song::getId, Comparator.reverseOrder()); - private Map songsSortedInLikeCountById; - private List sortedIds; + private static final Comparator COMPARATOR = + Comparator.comparing(Song::getTotalLikeCount, Comparator.reverseOrder()) + .thenComparing(Song::getId, Comparator.reverseOrder()); + private Map songsSortedInLikeCountById = new HashMap<>(); + private List sortedIds = new ArrayList<>(); private final EntityManager entityManager; @@ -47,11 +47,13 @@ private static Map getSortedSong(final List songs) { } public List getSongs() { - return songsSortedInLikeCountById.values().stream().toList(); + return sortedIds.stream() + .map(songsSortedInLikeCountById::get) + .toList(); } public List getSongs(final int limit) { - final List topSongIds = this.sortedIds.subList(0, Math.min(limit, this.sortedIds.size())); + final List topSongIds = sortedIds.subList(0, Math.min(limit, sortedIds.size())); return topSongIds.stream() .map(songsSortedInLikeCountById::get) @@ -123,12 +125,89 @@ public void like(final KillingPart killingPart, final KillingPartLike likeOnKill final KillingPart killingPartById = findKillingPart(killingPart, song); final boolean updated = killingPartById.like(likeOnKillingPart); if (updated) { - sortSongIds(); + reorder(song); + } + } + + public void reorder(final Song updatedSong) { + int currentSongIndex = sortedIds.indexOf(updatedSong.getId()); + + if (currentSongIndex == -1) { + return; + } + + if (shouldMoveForward(updatedSong, currentSongIndex)) { + moveLeft(updatedSong, currentSongIndex); + } + + if (shouldMoveBackward(updatedSong, currentSongIndex)) { + moveRight(updatedSong, currentSongIndex); } } - private void sortSongIds() { - sortedIds.sort(Comparator.comparing(songsSortedInLikeCountById::get, COMPARATOR)); + private boolean shouldMoveForward(final Song song, final int index) { + if (index == 0) { + return false; + } + + final Long prevSongId = sortedIds.get(index - 1); + final Song prevSong = songsSortedInLikeCountById.get(prevSongId); + + return index > 0 && shouldSwapWithPrevious(song, prevSong); + } + + private boolean shouldMoveBackward(final Song song, final int index) { + if (index == sortedIds.size() - 1) { + return false; + } + + final Long nextSongId = sortedIds.get(index + 1); + final Song nextSong = songsSortedInLikeCountById.get(nextSongId); + + return index < sortedIds.size() - 1 && shouldSwapWithNext(song, nextSong); + } + + private void moveLeft(final Song changedSong, final int songIndex) { + int currentSongIndex = songIndex; + + while (currentSongIndex > 0 && currentSongIndex < sortedIds.size() && + shouldSwapWithPrevious(changedSong, + songsSortedInLikeCountById.get(sortedIds.get(currentSongIndex - 1)))) { + swap(currentSongIndex, currentSongIndex - 1); + currentSongIndex--; + } + } + + private boolean shouldSwapWithPrevious(final Song song, final Song prevSong) { + final boolean hasSameTotalLikeCountAndLargerIdThanPrevSong = + song.getTotalLikeCount() == prevSong.getTotalLikeCount() && song.getId() > prevSong.getId(); + final boolean hasLargerTotalLikeCountThanPrevSong = song.getTotalLikeCount() > prevSong.getTotalLikeCount(); + + return hasLargerTotalLikeCountThanPrevSong || hasSameTotalLikeCountAndLargerIdThanPrevSong; + } + + private void swap(final int currentIndex, final int otherIndex) { + final Long prevIndex = sortedIds.get(currentIndex); + sortedIds.set(currentIndex, sortedIds.get(otherIndex)); + sortedIds.set(otherIndex, prevIndex); + } + + private void moveRight(final Song changedSong, final int songIndex) { + int currentSongIndex = songIndex; + + while (currentSongIndex < sortedIds.size() - 1 && currentSongIndex > 0 + && shouldSwapWithNext(changedSong, songsSortedInLikeCountById.get(sortedIds.get(currentSongIndex - 1)))) { + swap(currentSongIndex, currentSongIndex + 1); + currentSongIndex++; + } + } + + private boolean shouldSwapWithNext(final Song song, final Song nextSong) { + final boolean hasSameTotalLikeCountAndSmallerIdThanNextSong = + song.getTotalLikeCount() == nextSong.getTotalLikeCount() && song.getId() < nextSong.getId(); + final boolean hasSmallerTotalLikeCountThanNextSong = song.getTotalLikeCount() < nextSong.getTotalLikeCount(); + + return hasSmallerTotalLikeCountThanNextSong || hasSameTotalLikeCountAndSmallerIdThanNextSong; } private static KillingPart findKillingPart(final KillingPart killingPart, final Song song) { @@ -145,7 +224,7 @@ public void unlike(final KillingPart killingPart, final KillingPartLike unlikeOn final KillingPart killingPartById = findKillingPart(killingPart, song); final boolean updated = killingPartById.unlike(unlikeOnKillingPart); if (updated) { - sortSongIds(); + reorder(song); } } } diff --git a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java index e3a5064c..a83c6bdf 100644 --- a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java @@ -4,7 +4,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import jakarta.persistence.EntityManager; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -57,15 +56,13 @@ class SongServiceTest extends UsingJpaTest { @Autowired private ArtistRepository artistRepository; - @Autowired - private EntityManager entityManager; - - private final InMemorySongs inMemorySongs = new InMemorySongs(entityManager); + private InMemorySongs inMemorySongs; private SongService songService; @BeforeEach public void setUp() { + inMemorySongs = new InMemorySongs(entityManager); songService = new SongService( songRepository, killingPartRepository, @@ -121,8 +118,8 @@ void findById_exist_login_member() { //given final Member member = createAndSaveMember("email@naver.com", "email"); final Song song = registerNewSong("title"); + inMemorySongs.recreate(List.of(song)); addLikeToEachKillingParts(song, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); addMemberPartToSong(10, 5, song, member); //when @@ -166,7 +163,7 @@ private MemberPart addMemberPartToSong(final int startSecond, final int length, void findById_exist_not_login_member() { //given final Song song = registerNewSong("title"); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); + inMemorySongs.recreate(List.of(song)); //when인 saveAndClearEntityManager(); @@ -204,6 +201,7 @@ void findById_exist_not_login_member() { void findById_notExist() { //given final Member member = createAndSaveMember("email@naver.com", "email"); + inMemorySongs.recreate(List.of()); //when //then @@ -231,8 +229,8 @@ void showHighLikedSongs() { addLikeToEachKillingParts(thirdSong, member2); addLikeToEachKillingParts(fourthSong, member1); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); saveAndClearEntityManager(); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); // when final List result = songService.showHighLikedSongs(); @@ -308,7 +306,6 @@ void firstFindByMember() { // 4, 3, 5, 2, 1 addLikeToEachKillingParts(thirdSong, member); addLikeToEachKillingParts(fourthSong, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); // 1, 2, 3 노래에 memberPart 추가 addMemberPartToSong(10, 5, firstSong, member); @@ -317,6 +314,7 @@ void firstFindByMember() { addMemberPartToSong(10, 5, fourthSong, member); saveAndClearEntityManager(); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); // when final SongSwipeResponse result = @@ -355,6 +353,8 @@ void firstFindByAnonymous() { final Member member = createAndSaveMember("first@naver.com", "first"); final Long notExistSongId = Long.MAX_VALUE; + saveAndClearEntityManager(); + // when // then assertThatThrownBy( @@ -389,8 +389,6 @@ void findSongByIdForBeforeSwipe() { addLikeToEachKillingParts(fourthSong, member2); addLikeToEachKillingParts(firstSong, member2); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); - addMemberPartToSong(10, 5, firstSong, member); addMemberPartToSong(10, 5, secondSong, member); addMemberPartToSong(10, 5, standardSong, member); @@ -399,6 +397,7 @@ void findSongByIdForBeforeSwipe() { // 정렬 순서: 2L, 4L, 1L, 5L, 3L saveAndClearEntityManager(); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); // when final List beforeResponses = @@ -432,7 +431,6 @@ void findSongByIdForAfterSwipe() { addLikeToEachKillingParts(secondSong, member2); addLikeToEachKillingParts(standardSong, member2); addLikeToEachKillingParts(firstSong, member2); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); addMemberPartToSong(10, 5, firstSong, member); addMemberPartToSong(10, 5, secondSong, member); @@ -441,8 +439,8 @@ void findSongByIdForAfterSwipe() { addMemberPartToSong(10, 5, fifthSong, member); // 정렬 순서: 2L, 4L, 1L, 5L, 3L - saveAndClearEntityManager(); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); // when final List afterResponses = @@ -483,9 +481,9 @@ void findSongsByGenre() { addLikeToEachKillingParts(song1, member); addLikeToEachKillingParts(song1, secondMember); addLikeToEachKillingParts(song3, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); // 정렬 순서: 2L, 1L, 3L, 5L, 4L + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); saveAndClearEntityManager(); // when @@ -508,9 +506,9 @@ void findSongById() { final Song song = registerNewSong("title"); final Member member = createAndSaveMember("email@email.com", "nickname"); addLikeToEachKillingParts(song, member); - inMemorySongs.recreate(songRepository.findAllWithKillingParts()); addMemberPartToSong(10, 5, song, member); saveAndClearEntityManager(); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); // when final SongResponse response = songService.findSongById(song.getId(), diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java index c7002a77..4aef7e0d 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java @@ -19,9 +19,11 @@ import shook.shook.member.domain.repository.MemberRepository; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; +import shook.shook.song.domain.repository.SongRepository; @Sql("classpath:/killingpart/initialize_killing_part_song.sql") @SpringBootTest @@ -29,6 +31,7 @@ class KillingPartLikeConcurrencyTest { private static KillingPart SAVED_KILLING_PART; private static Member SAVED_MEMBER; + private static Song SAVED_SONG; @Autowired private KillingPartRepository killingPartRepository; @@ -45,11 +48,15 @@ class KillingPartLikeConcurrencyTest { @Autowired private InMemorySongs inMemorySongs; + @Autowired + private SongRepository songRepository; + private KillingPartLikeService likeService; private TransactionTemplate transactionTemplate; @BeforeEach void setUp() { + SAVED_SONG = songRepository.findById(1L).get(); SAVED_KILLING_PART = killingPartRepository.findById(1L).get(); SAVED_MEMBER = memberRepository.findById(1L).get(); likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository, @@ -64,6 +71,7 @@ void likeByMultiplePeople() throws InterruptedException { // given final Member first = SAVED_MEMBER; final Member second = memberRepository.save(new Member("second@gmail.com", "second")); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); // when ExecutorService executorService = Executors.newFixedThreadPool(2); @@ -91,8 +99,10 @@ void likeByMultiplePeople() throws InterruptedException { Thread.sleep(1000); // then - final KillingPart killingPart = killingPartRepository.findById(SAVED_KILLING_PART.getId()).get(); - assertThat(killingPart.getLikeCount()).isEqualTo(2); + final KillingPart killingPart = inMemorySongs.getSongById(SAVED_SONG.getId()).getKillingParts().stream() + .filter(kp -> kp.getId().equals(SAVED_KILLING_PART.getId())) + .findAny().get(); + assertThat(killingPart.getAtomicLikeCount().get()).isEqualTo(2); } @Disabled("UPDATE + 1 사용 시 한 사용자의 동시에 도착하는 좋아요 요청 동시성 문제 발생") diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java index 5a06fbe5..de22b50e 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java @@ -15,10 +15,12 @@ import shook.shook.member.exception.MemberException; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.KillingPartLike; import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; +import shook.shook.song.domain.repository.SongRepository; import shook.shook.song.exception.killingpart.KillingPartException; import shook.shook.support.UsingJpaTest; @@ -29,6 +31,7 @@ class KillingPartLikeServiceTest extends UsingJpaTest { private static final long UNSAVED_KILLING_PART_ID = Long.MAX_VALUE; private static KillingPart SAVED_KILLING_PART; private static Member SAVED_MEMBER; + private static Song SAVED_SONG; @Autowired private KillingPartRepository killingPartRepository; @@ -39,14 +42,20 @@ class KillingPartLikeServiceTest extends UsingJpaTest { @Autowired private MemberRepository memberRepository; + @Autowired + private SongRepository songRepository; + private KillingPartLikeService likeService; + private InMemorySongs inMemorySongs; @BeforeEach void setUp() { + SAVED_SONG = songRepository.findById(1L).get(); SAVED_KILLING_PART = killingPartRepository.findById(1L).get(); SAVED_MEMBER = memberRepository.findById(1L).get(); - likeService = new KillingPartLikeService(killingPartRepository, memberRepository, - killingPartLikeRepository, new InMemorySongs(entityManager)); + inMemorySongs = new InMemorySongs(entityManager); + likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository, + inMemorySongs); } @DisplayName("킬링파트 좋아요를 누른다.") @@ -58,6 +67,7 @@ class Create { void create_newLike() { // given // when + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); saveAndClearEntityManager(); @@ -65,8 +75,10 @@ void create_newLike() { // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Optional updatedKillingPart = inMemorySongs.getSongById(SAVED_SONG.getId()) + .getKillingParts().stream() + .filter(killingPart -> killingPart.getId().equals(SAVED_KILLING_PART.getId())) + .findAny(); assertThat(savedLike).isPresent() .get() @@ -81,6 +93,7 @@ void create_newLike() { @Test void create_updateLike_exist() { // given + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), @@ -95,8 +108,10 @@ void create_updateLike_exist() { // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Optional updatedKillingPart = inMemorySongs.getSongById(SAVED_SONG.getId()) + .getKillingParts().stream() + .filter(killingPart -> killingPart.getId().equals(SAVED_KILLING_PART.getId())) + .findAny(); assertThat(savedLike).isPresent() .get() @@ -111,6 +126,7 @@ void create_updateLike_exist() { @Test void create_noAction() { // given + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); saveAndClearEntityManager(); @@ -123,8 +139,10 @@ void create_noAction() { // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Optional updatedKillingPart = inMemorySongs.getSongById(SAVED_SONG.getId()) + .getKillingParts().stream() + .filter(killingPart -> killingPart.getId().equals(SAVED_KILLING_PART.getId())) + .findAny(); assertThat(savedLike).isPresent() .get() @@ -187,6 +205,7 @@ void delete_noAction() { @Test void delete_alreadyDeleted_noAction() { // given + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), @@ -217,6 +236,7 @@ void delete_alreadyDeleted_noAction() { @Test void create_noAction() { // given + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); saveAndClearEntityManager(); diff --git a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java index 25273817..b9535c11 100644 --- a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java +++ b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java @@ -43,7 +43,7 @@ void setUp() { @Test void recreate() { // given - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); likeAllKillingPartsInSong(songs.get(0)); likeAllKillingPartsInSong(songs.get(1)); @@ -71,14 +71,14 @@ private void likeAllKillingPartsInSong(final Song song) { @Test void getSongById() { // given - final List songs = songRepository.findAllWithKillingParts(); - inMemorySongs.recreate(songs); + inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); // when + final List allSongs = inMemorySongs.getSongs(); final Song foundSong = inMemorySongs.getSongById(4L); // then - final Song expectedSong = songs.get(0); + final Song expectedSong = allSongs.get(0); assertThat(foundSong).isEqualTo(expectedSong); } @@ -86,7 +86,7 @@ void getSongById() { @Test void getPrevLikedSongs() { // given - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); @@ -110,7 +110,7 @@ void getPrevLikedSongs() { @Test void getNextLikedSongs() { // given - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); @@ -133,7 +133,7 @@ void getNextLikedSongs() { @DisplayName("특정 장르 노래에 대해 1. 좋아요 수가 더 적거나 2. 좋아요 수가 같은 경우 id가 더 작은 노래 목록을 조회한다.") @Test void getSortedSongsByGenre() { - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); @@ -155,7 +155,7 @@ void getSortedSongsByGenre() { @DisplayName("특정 장르 노래에 대해 1. 좋아요 수가 더 많거나 2. 좋아요 수가 같은 경우 id가 더 큰 노래 목록을 조회한다.") @Test void getPrevLikedSongByGenre() { - final List songs = songRepository.findAllWithKillingParts(); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); final Song firstSong = songs.get(0); final Song secondSong = songs.get(1); final Song thirdSong = songs.get(2); diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java index b4e68827..ab405e0a 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/KillingPartLikesTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.List; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -135,7 +135,7 @@ void getLikes() { KILLING_PART.unlike(newLike); // when - final List likes = LIKES.getLikes(); + final Set likes = LIKES.getLikes(); // then assertThat(LIKES.getSize()).isEqualTo(1); diff --git a/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java b/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java index 653d2995..f9cedc93 100644 --- a/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/HighLikedSongControllerTest.java @@ -51,16 +51,15 @@ void setUp() { @Test void showHighLikedSongs() { //given + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); // 정렬 순서 1L 2L 4L 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final List responses = RestAssured.given().log().all() .when().log().all() @@ -94,16 +93,15 @@ void showHighLikedSongs() { void showHighLikedSongsWithGenre() { // given final String genre = "DANCE"; + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, - new KillingPartLikeRequest(true)); + new KillingPartLikeRequest(true)); // 정렬 순서 1L 4L 3L - inMemorySongsScheduler.recreateCachedSong(); - // when final List responses = RestAssured.given().log().all() .queryParam("genre", genre) @@ -118,11 +116,11 @@ void showHighLikedSongsWithGenre() { assertAll( () -> assertThat(responses).hasSize(3), () -> assertThat(responses.stream() - .map(HighLikedSongResponse::getId) - .toList()) + .map(HighLikedSongResponse::getId) + .toList()) .containsExactly(1L, 4L, 3L), () -> assertThat(responses.stream() - .map(HighLikedSongResponse::getTotalLikeCount)) + .map(HighLikedSongResponse::getTotalLikeCount)) .containsExactly(2L, 0L, 0L) ); } diff --git a/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java b/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java index 276805da..bf77d037 100644 --- a/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/SongControllerTest.java @@ -57,6 +57,7 @@ void findSongById() { final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, MEMBER_NICKNAME); final Long songId = 1L; + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, @@ -65,7 +66,6 @@ void findSongById() { new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_3, MEMBER_ID, new KillingPartLikeRequest(true)); - inMemorySongsScheduler.recreateCachedSong(); memberPartService.register(songId, MEMBER_ID, new MemberPartRegisterRequest(0, 10)); diff --git a/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java b/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java index 494b106b..fe25d891 100644 --- a/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/SongSwipeControllerTest.java @@ -25,7 +25,6 @@ import shook.shook.song.application.killingpart.dto.HighLikedSongResponse; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; -@SuppressWarnings("NonAsciiCharacters") @Sql("classpath:/killingpart/initialize_killing_part_song.sql") @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class SongSwipeControllerTest { @@ -62,6 +61,7 @@ void showSongById() { final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); final Long songId = 2L; + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -69,7 +69,6 @@ void showSongById() { likeService.updateLikeStatus(SECOND_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); - inMemorySongsScheduler.recreateCachedSong(); // 정렬 순서: 1L, 2L, 4L, 3L //when final SongSwipeResponse response = RestAssured.given().log().all() @@ -97,6 +96,8 @@ void showSongsBeforeSongWithId() { // given final Long songId = 2L; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -105,8 +106,6 @@ void showSongsBeforeSongWithId() { memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L, 2L - inMemorySongsScheduler.recreateCachedSong(); - //when final List response = RestAssured.given().log().all() .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) @@ -214,6 +213,7 @@ void findSongsByGenreForSwipe() { final String genre = "DANCE"; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -224,8 +224,6 @@ void findSongsByGenreForSwipe() { memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final SongSwipeResponse response = RestAssured.given().log().all() .queryParam("genre", genre) @@ -258,6 +256,7 @@ void showPrevSongsWithGenre() { final String genre = "DANCE"; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -266,8 +265,6 @@ void showPrevSongsWithGenre() { memberPartService.register(1L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final List response = RestAssured.given().log().all() .queryParam("genre", genre) @@ -295,6 +292,7 @@ void showNextSongsWithGenre() { final String genre = "DANCE"; final String accessToken = tokenProvider.createAccessToken(MEMBER_ID, "nickname"); + inMemorySongsScheduler.recreateCachedSong(); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_1, MEMBER_ID, new KillingPartLikeRequest(true)); likeService.updateLikeStatus(FIRST_SONG_KILLING_PART_ID_2, MEMBER_ID, @@ -305,8 +303,6 @@ void showNextSongsWithGenre() { memberPartService.register(4L, MEMBER_ID, new MemberPartRegisterRequest(5, 5)); // 정렬 순서 1L, 4L, 3L - inMemorySongsScheduler.recreateCachedSong(); - //when final List response = RestAssured.given().log().all() .queryParam("genre", genre) From 0f1c9e3e96670e5d24f728c1c999d1fa647e3f06 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:34:27 +0900 Subject: [PATCH 07/19] =?UTF-8?q?refactor:=20EntityManager=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C,=20=ED=95=84=EB=93=9C=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/InMemorySongsScheduler.java | 17 ++- .../shook/song/domain/InMemorySongs.java | 109 ++++++++---------- .../song/application/SongServiceTest.java | 20 ++-- .../KillingPartLikeConcurrencyTest.java | 2 +- .../KillingPartLikeServiceTest.java | 12 +- .../shook/song/domain/InMemorySongsTest.java | 14 +-- 6 files changed, 88 insertions(+), 86 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java index 505bf50e..323f95d9 100644 --- a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java +++ b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java @@ -1,12 +1,15 @@ package shook.shook.song.application; import jakarta.annotation.PostConstruct; +import jakarta.persistence.EntityManager; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; import shook.shook.song.domain.repository.SongRepository; @RequiredArgsConstructor @@ -17,6 +20,7 @@ public class InMemorySongsScheduler { private final SongRepository songRepository; private final InMemorySongs inMemorySongs; + private final EntityManager entityManager; @PostConstruct public void initialize() { @@ -26,6 +30,17 @@ public void initialize() { @Scheduled(cron = "${schedules.in-memory-song.cron}") public void recreateCachedSong() { log.info("InMemorySongsScheduler worked"); - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + final List songs = songRepository.findAllWithKillingPartsAndLikes(); + detachSongs(songs); + inMemorySongs.refreshSongs(songs); + } + + private void detachSongs(final List songs) { + songs.stream() + .peek(entityManager::detach) + .flatMap(song -> song.getKillingParts().stream()) + .peek(entityManager::detach) + .flatMap(killingPart -> killingPart.getKillingPartLikes().stream()) + .forEach(entityManager::detach); } } diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 0bb8ce9d..0df3b92d 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -1,6 +1,5 @@ package shook.shook.song.domain; -import jakarta.persistence.EntityManager; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -22,47 +21,35 @@ public class InMemorySongs { private static final Comparator COMPARATOR = Comparator.comparing(Song::getTotalLikeCount, Comparator.reverseOrder()) .thenComparing(Song::getId, Comparator.reverseOrder()); - private Map songsSortedInLikeCountById = new HashMap<>(); - private List sortedIds = new ArrayList<>(); - private final EntityManager entityManager; + private Map songs = new HashMap<>(); + private List sortedSongIds = new ArrayList<>(); - public void recreate(final List songs) { - songsSortedInLikeCountById = getSortedSong(songs); - sortedIds = new ArrayList<>(songsSortedInLikeCountById.keySet().stream() - .sorted(Comparator.comparing(songsSortedInLikeCountById::get, COMPARATOR)) - .toList()); - - songsSortedInLikeCountById.values().stream() - .peek(entityManager::detach) - .flatMap(song -> song.getKillingParts().stream()) - .peek(entityManager::detach) - .flatMap(killingPart -> killingPart.getKillingPartLikes().stream()) - .forEach(entityManager::detach); - } - - private static Map getSortedSong(final List songs) { - return songs.stream() + public void refreshSongs(final List songs) { + this.songs = songs.stream() .collect(Collectors.toMap(Song::getId, song -> song, (prev, update) -> update, HashMap::new)); + this.sortedSongIds = new ArrayList<>(this.songs.keySet().stream() + .sorted(Comparator.comparing(this.songs::get, COMPARATOR)) + .toList()); } public List getSongs() { - return sortedIds.stream() - .map(songsSortedInLikeCountById::get) + return sortedSongIds.stream() + .map(songs::get) .toList(); } public List getSongs(final int limit) { - final List topSongIds = sortedIds.subList(0, Math.min(limit, sortedIds.size())); + final List topSongIds = sortedSongIds.subList(0, Math.min(limit, sortedSongIds.size())); return topSongIds.stream() - .map(songsSortedInLikeCountById::get) + .map(songs::get) .toList(); } public List getSortedSongsByGenre(final Genre genre) { - return sortedIds.stream() - .map(songsSortedInLikeCountById::get) + return sortedSongIds.stream() + .map(songs::get) .filter(song -> song.getGenre() == genre) .toList(); } @@ -74,8 +61,8 @@ public List getSortedSongsByGenre(final Genre genre, final int limit) { } public Song getSongById(final Long id) { - if (songsSortedInLikeCountById.containsKey(id)) { - return songsSortedInLikeCountById.get(id); + if (songs.containsKey(id)) { + return songs.get(id); } throw new SongException.SongNotExistException(Map.of("song id", String.valueOf(id))); } @@ -100,28 +87,28 @@ public List getNextLikedSongByGenre(final Song currentSong, final Genre ge } public List getPrevLikedSongs(final Song currentSong, final int prevSongCount) { - final int currentSongIndex = sortedIds.indexOf(currentSong.getId()); + final int currentSongIndex = sortedSongIds.indexOf(currentSong.getId()); - return sortedIds.subList(Math.max(0, currentSongIndex - prevSongCount), currentSongIndex).stream() - .map(songsSortedInLikeCountById::get) + return sortedSongIds.subList(Math.max(0, currentSongIndex - prevSongCount), currentSongIndex).stream() + .map(songs::get) .toList(); } public List getNextLikedSongs(final Song currentSong, final int nextSongCount) { - final int currentSongIndex = sortedIds.indexOf(currentSong.getId()); + final int currentSongIndex = sortedSongIds.indexOf(currentSong.getId()); - if (currentSongIndex == sortedIds.size() - 1) { + if (currentSongIndex == sortedSongIds.size() - 1) { return Collections.emptyList(); } - return sortedIds.subList(Math.min(currentSongIndex + 1, sortedIds.size() - 1), - Math.min(sortedIds.size(), currentSongIndex + nextSongCount + 1)).stream() - .map(songsSortedInLikeCountById::get) + return sortedSongIds.subList(Math.min(currentSongIndex + 1, sortedSongIds.size() - 1), + Math.min(sortedSongIds.size(), currentSongIndex + nextSongCount + 1)).stream() + .map(songs::get) .toList(); } public void like(final KillingPart killingPart, final KillingPartLike likeOnKillingPart) { - final Song song = songsSortedInLikeCountById.get(killingPart.getSong().getId()); + final Song song = songs.get(killingPart.getSong().getId()); final KillingPart killingPartById = findKillingPart(killingPart, song); final boolean updated = killingPartById.like(likeOnKillingPart); if (updated) { @@ -129,8 +116,17 @@ public void like(final KillingPart killingPart, final KillingPartLike likeOnKill } } + private static KillingPart findKillingPart(final KillingPart killingPart, final Song song) { + return song.getKillingParts().stream() + .filter(kp -> kp.equals(killingPart)) + .findAny() + .orElseThrow( + () -> new KillingPartException.PartNotExistException( + Map.of("killing part id", String.valueOf(killingPart.getId())))); + } + public void reorder(final Song updatedSong) { - int currentSongIndex = sortedIds.indexOf(updatedSong.getId()); + int currentSongIndex = sortedSongIds.indexOf(updatedSong.getId()); if (currentSongIndex == -1) { return; @@ -150,29 +146,29 @@ private boolean shouldMoveForward(final Song song, final int index) { return false; } - final Long prevSongId = sortedIds.get(index - 1); - final Song prevSong = songsSortedInLikeCountById.get(prevSongId); + final Long prevSongId = sortedSongIds.get(index - 1); + final Song prevSong = songs.get(prevSongId); return index > 0 && shouldSwapWithPrevious(song, prevSong); } private boolean shouldMoveBackward(final Song song, final int index) { - if (index == sortedIds.size() - 1) { + if (index == sortedSongIds.size() - 1) { return false; } - final Long nextSongId = sortedIds.get(index + 1); - final Song nextSong = songsSortedInLikeCountById.get(nextSongId); + final Long nextSongId = sortedSongIds.get(index + 1); + final Song nextSong = songs.get(nextSongId); - return index < sortedIds.size() - 1 && shouldSwapWithNext(song, nextSong); + return index < sortedSongIds.size() - 1 && shouldSwapWithNext(song, nextSong); } private void moveLeft(final Song changedSong, final int songIndex) { int currentSongIndex = songIndex; - while (currentSongIndex > 0 && currentSongIndex < sortedIds.size() && + while (currentSongIndex > 0 && currentSongIndex < sortedSongIds.size() && shouldSwapWithPrevious(changedSong, - songsSortedInLikeCountById.get(sortedIds.get(currentSongIndex - 1)))) { + songs.get(sortedSongIds.get(currentSongIndex - 1)))) { swap(currentSongIndex, currentSongIndex - 1); currentSongIndex--; } @@ -187,16 +183,16 @@ private boolean shouldSwapWithPrevious(final Song song, final Song prevSong) { } private void swap(final int currentIndex, final int otherIndex) { - final Long prevIndex = sortedIds.get(currentIndex); - sortedIds.set(currentIndex, sortedIds.get(otherIndex)); - sortedIds.set(otherIndex, prevIndex); + final Long prevIndex = sortedSongIds.get(currentIndex); + sortedSongIds.set(currentIndex, sortedSongIds.get(otherIndex)); + sortedSongIds.set(otherIndex, prevIndex); } private void moveRight(final Song changedSong, final int songIndex) { int currentSongIndex = songIndex; - while (currentSongIndex < sortedIds.size() - 1 && currentSongIndex > 0 - && shouldSwapWithNext(changedSong, songsSortedInLikeCountById.get(sortedIds.get(currentSongIndex - 1)))) { + while (currentSongIndex < sortedSongIds.size() - 1 && currentSongIndex > 0 + && shouldSwapWithNext(changedSong, songs.get(sortedSongIds.get(currentSongIndex - 1)))) { swap(currentSongIndex, currentSongIndex + 1); currentSongIndex++; } @@ -210,17 +206,8 @@ private boolean shouldSwapWithNext(final Song song, final Song nextSong) { return hasSmallerTotalLikeCountThanNextSong || hasSameTotalLikeCountAndSmallerIdThanNextSong; } - private static KillingPart findKillingPart(final KillingPart killingPart, final Song song) { - return song.getKillingParts().stream() - .filter(kp -> kp.equals(killingPart)) - .findAny() - .orElseThrow( - () -> new KillingPartException.PartNotExistException( - Map.of("killing part id", String.valueOf(killingPart.getId())))); - } - public void unlike(final KillingPart killingPart, final KillingPartLike unlikeOnKillingPart) { - final Song song = songsSortedInLikeCountById.get(killingPart.getSong().getId()); + final Song song = songs.get(killingPart.getSong().getId()); final KillingPart killingPartById = findKillingPart(killingPart, song); final boolean updated = killingPartById.unlike(unlikeOnKillingPart); if (updated) { diff --git a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java index a83c6bdf..534b5f54 100644 --- a/backend/src/test/java/shook/shook/song/application/SongServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/SongServiceTest.java @@ -62,7 +62,7 @@ class SongServiceTest extends UsingJpaTest { @BeforeEach public void setUp() { - inMemorySongs = new InMemorySongs(entityManager); + inMemorySongs = new InMemorySongs(); songService = new SongService( songRepository, killingPartRepository, @@ -118,7 +118,7 @@ void findById_exist_login_member() { //given final Member member = createAndSaveMember("email@naver.com", "email"); final Song song = registerNewSong("title"); - inMemorySongs.recreate(List.of(song)); + inMemorySongs.refreshSongs(List.of(song)); addLikeToEachKillingParts(song, member); addMemberPartToSong(10, 5, song, member); @@ -163,7 +163,7 @@ private MemberPart addMemberPartToSong(final int startSecond, final int length, void findById_exist_not_login_member() { //given final Song song = registerNewSong("title"); - inMemorySongs.recreate(List.of(song)); + inMemorySongs.refreshSongs(List.of(song)); //when인 saveAndClearEntityManager(); @@ -201,7 +201,7 @@ void findById_exist_not_login_member() { void findById_notExist() { //given final Member member = createAndSaveMember("email@naver.com", "email"); - inMemorySongs.recreate(List.of()); + inMemorySongs.refreshSongs(List.of()); //when //then @@ -229,7 +229,7 @@ void showHighLikedSongs() { addLikeToEachKillingParts(thirdSong, member2); addLikeToEachKillingParts(fourthSong, member1); - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); saveAndClearEntityManager(); // when @@ -314,7 +314,7 @@ void firstFindByMember() { addMemberPartToSong(10, 5, fourthSong, member); saveAndClearEntityManager(); - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final SongSwipeResponse result = @@ -397,7 +397,7 @@ void findSongByIdForBeforeSwipe() { // 정렬 순서: 2L, 4L, 1L, 5L, 3L saveAndClearEntityManager(); - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final List beforeResponses = @@ -440,7 +440,7 @@ void findSongByIdForAfterSwipe() { // 정렬 순서: 2L, 4L, 1L, 5L, 3L saveAndClearEntityManager(); - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final List afterResponses = @@ -483,7 +483,7 @@ void findSongsByGenre() { addLikeToEachKillingParts(song3, member); // 정렬 순서: 2L, 1L, 3L, 5L, 4L - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); saveAndClearEntityManager(); // when @@ -508,7 +508,7 @@ void findSongById() { addLikeToEachKillingParts(song, member); addMemberPartToSong(10, 5, song, member); saveAndClearEntityManager(); - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final SongResponse response = songService.findSongById(song.getId(), diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java index 4aef7e0d..e4bb18aa 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java @@ -71,7 +71,7 @@ void likeByMultiplePeople() throws InterruptedException { // given final Member first = SAVED_MEMBER; final Member second = memberRepository.save(new Member("second@gmail.com", "second")); - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when ExecutorService executorService = Executors.newFixedThreadPool(2); diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java index de22b50e..18c6284c 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java @@ -53,7 +53,7 @@ void setUp() { SAVED_SONG = songRepository.findById(1L).get(); SAVED_KILLING_PART = killingPartRepository.findById(1L).get(); SAVED_MEMBER = memberRepository.findById(1L).get(); - inMemorySongs = new InMemorySongs(entityManager); + inMemorySongs = new InMemorySongs(); likeService = new KillingPartLikeService(killingPartRepository, memberRepository, killingPartLikeRepository, inMemorySongs); } @@ -67,7 +67,7 @@ class Create { void create_newLike() { // given // when - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); saveAndClearEntityManager(); @@ -93,7 +93,7 @@ void create_newLike() { @Test void create_updateLike_exist() { // given - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), @@ -126,7 +126,7 @@ void create_updateLike_exist() { @Test void create_noAction() { // given - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); saveAndClearEntityManager(); @@ -205,7 +205,7 @@ void delete_noAction() { @Test void delete_alreadyDeleted_noAction() { // given - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), @@ -236,7 +236,7 @@ void delete_alreadyDeleted_noAction() { @Test void create_noAction() { // given - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), SAVED_MEMBER.getId(), new KillingPartLikeRequest(true)); saveAndClearEntityManager(); diff --git a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java index b9535c11..e849e27a 100644 --- a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java +++ b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java @@ -36,7 +36,7 @@ class InMemorySongsTest extends UsingJpaTest { @BeforeEach void setUp() { MEMBER = memberRepository.findById(1L).get(); - inMemorySongs = new InMemorySongs(entityManager); + inMemorySongs = new InMemorySongs(); } @DisplayName("InMemorySong 을 1.좋아요 순, 2. id 순으로 정렬된 노래로 초기화한다.") @@ -48,7 +48,7 @@ void recreate() { likeAllKillingPartsInSong(songs.get(1)); // when - inMemorySongs.recreate(songs); + inMemorySongs.refreshSongs(songs); // 정렬 순서: 2L, 1L, 4L // then @@ -71,7 +71,7 @@ private void likeAllKillingPartsInSong(final Song song) { @Test void getSongById() { // given - inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes()); + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); // when final List allSongs = inMemorySongs.getSongs(); @@ -93,7 +93,7 @@ void getPrevLikedSongs() { final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // second, first, fourth, third + inMemorySongs.refreshSongs(songs); // second, first, fourth, third // when final List prevLikedSongs = inMemorySongs.getPrevLikedSongs(thirdSong, 2); @@ -117,7 +117,7 @@ void getNextLikedSongs() { final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // second, first, fourth, third + inMemorySongs.refreshSongs(songs); // second, first, fourth, third // when final List nextLikedSongs = inMemorySongs.getNextLikedSongs(secondSong, 2); @@ -140,7 +140,7 @@ void getSortedSongsByGenre() { final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // first, fourth, third + inMemorySongs.refreshSongs(songs); // first, fourth, third // when final List prevLikedSongs = inMemorySongs.getPrevLikedSongByGenre(firstSong, Genre.DANCE, 2); @@ -162,7 +162,7 @@ void getPrevLikedSongByGenre() { final Song fourthSong = songs.get(3); likeAllKillingPartsInSong(firstSong); likeAllKillingPartsInSong(secondSong); - inMemorySongs.recreate(songs); // first, fourth, third + inMemorySongs.refreshSongs(songs); // first, fourth, third // when final List prevLikedSongs = inMemorySongs.getNextLikedSongByGenre(thirdSong, Genre.DANCE, 2); From fd5f35e18857e2303bf836e8a85b216c6dd68725 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:41:50 +0900 Subject: [PATCH 08/19] =?UTF-8?q?refactor:=20=EC=A1=B0=EA=B1=B4=EB=AC=B8?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/song/domain/InMemorySongs.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 0df3b92d..37ff0f62 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -133,11 +133,11 @@ public void reorder(final Song updatedSong) { } if (shouldMoveForward(updatedSong, currentSongIndex)) { - moveLeft(updatedSong, currentSongIndex); + moveForward(updatedSong, currentSongIndex); } if (shouldMoveBackward(updatedSong, currentSongIndex)) { - moveRight(updatedSong, currentSongIndex); + moveBackward(updatedSong, currentSongIndex); } } @@ -163,17 +163,21 @@ private boolean shouldMoveBackward(final Song song, final int index) { return index < sortedSongIds.size() - 1 && shouldSwapWithNext(song, nextSong); } - private void moveLeft(final Song changedSong, final int songIndex) { + private void moveForward(final Song changedSong, final int songIndex) { int currentSongIndex = songIndex; - while (currentSongIndex > 0 && currentSongIndex < sortedSongIds.size() && - shouldSwapWithPrevious(changedSong, - songs.get(sortedSongIds.get(currentSongIndex - 1)))) { + while (canSwapWithPreviousSong(changedSong, currentSongIndex)) { swap(currentSongIndex, currentSongIndex - 1); currentSongIndex--; } } + private boolean canSwapWithPreviousSong(final Song changedSong, final int currentSongIndex) { + return currentSongIndex > 0 && currentSongIndex < sortedSongIds.size() && + shouldSwapWithPrevious(changedSong, + songs.get(sortedSongIds.get(currentSongIndex - 1))); + } + private boolean shouldSwapWithPrevious(final Song song, final Song prevSong) { final boolean hasSameTotalLikeCountAndLargerIdThanPrevSong = song.getTotalLikeCount() == prevSong.getTotalLikeCount() && song.getId() > prevSong.getId(); @@ -188,16 +192,20 @@ private void swap(final int currentIndex, final int otherIndex) { sortedSongIds.set(otherIndex, prevIndex); } - private void moveRight(final Song changedSong, final int songIndex) { + private void moveBackward(final Song changedSong, final int songIndex) { int currentSongIndex = songIndex; - while (currentSongIndex < sortedSongIds.size() - 1 && currentSongIndex > 0 - && shouldSwapWithNext(changedSong, songs.get(sortedSongIds.get(currentSongIndex - 1)))) { + while (canSwapWithNextSong(changedSong, currentSongIndex)) { swap(currentSongIndex, currentSongIndex + 1); currentSongIndex++; } } + private boolean canSwapWithNextSong(final Song changedSong, final int currentSongIndex) { + return currentSongIndex < sortedSongIds.size() - 1 && currentSongIndex > 0 + && shouldSwapWithNext(changedSong, songs.get(sortedSongIds.get(currentSongIndex - 1))); + } + private boolean shouldSwapWithNext(final Song song, final Song nextSong) { final boolean hasSameTotalLikeCountAndSmallerIdThanNextSong = song.getTotalLikeCount() == nextSong.getTotalLikeCount() && song.getId() < nextSong.getId(); From 2c0a4ee20de6403719194b8bfb7b03503ea08d2d Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:42:16 +0900 Subject: [PATCH 09/19] =?UTF-8?q?refactor:=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20private=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/shook/shook/song/domain/InMemorySongs.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 37ff0f62..a10017f7 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -125,7 +125,7 @@ private static KillingPart findKillingPart(final KillingPart killingPart, final Map.of("killing part id", String.valueOf(killingPart.getId())))); } - public void reorder(final Song updatedSong) { + private void reorder(final Song updatedSong) { int currentSongIndex = sortedSongIds.indexOf(updatedSong.getId()); if (currentSongIndex == -1) { From 12ae35c4691f7ba667a8cd4b1d39095eb4e86cf4 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:45:19 +0900 Subject: [PATCH 10/19] =?UTF-8?q?refactor:=20KillingPart=20=EA=B0=80=20Ato?= =?UTF-8?q?micInteger=20=EB=A5=BC=20=ED=95=84=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=96=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../song/domain/killingpart/KillingPart.java | 23 ++++++------------- .../killingpart/LikeCountConverter.java | 19 +++++++++++++++ .../KillingPartLikeConcurrencyTest.java | 2 +- 3 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java index 9ccc0c1a..2ff3c303 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/KillingPart.java @@ -1,6 +1,7 @@ package shook.shook.song.domain.killingpart; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -10,7 +11,6 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.PostLoad; import jakarta.persistence.PrePersist; import jakarta.persistence.Table; import java.time.LocalDateTime; @@ -62,10 +62,9 @@ public class KillingPart { @Embedded private final KillingPartLikes killingPartLikes = new KillingPartLikes(); + @Convert(converter = LikeCountConverter.class) @Column(nullable = false) - private int likeCount = 0; - - private transient AtomicInteger atomicLikeCount; + private AtomicInteger likeCount; @Column(nullable = false, updatable = false) private LocalDateTime createdAt = LocalDateTime.now().truncatedTo(ChronoUnit.MICROS); @@ -86,8 +85,7 @@ private KillingPart( this.startSecond = startSecond; this.length = new PartLength(length); this.song = song; - this.likeCount = likeCount; - this.atomicLikeCount = new AtomicInteger(likeCount); + this.likeCount = new AtomicInteger(likeCount); } private KillingPart(final int startSecond, final int length) { @@ -107,11 +105,6 @@ public static KillingPart forSave(final int startSecond, final int length) { return new KillingPart(startSecond, length); } - @PostLoad - private void postLoad() { - this.atomicLikeCount = new AtomicInteger(likeCount); - } - public void addComment(final KillingPartComment comment) { if (comment.isBelongToOtherKillingPart(this)) { throw new KillingPartCommentException.CommentForOtherPartException( @@ -128,8 +121,7 @@ public boolean like(final KillingPartLike likeToAdd) { validateLikeUpdate(likeToAdd); final boolean isLikeCreated = killingPartLikes.addLike(likeToAdd); if (isLikeCreated) { - this.likeCount++; - atomicLikeCount.incrementAndGet(); + likeCount.incrementAndGet(); return true; } return false; @@ -153,8 +145,7 @@ public boolean unlike(final KillingPartLike likeToDelete) { validateLikeUpdate(likeToDelete); final boolean isLikeDeleted = killingPartLikes.deleteLike(likeToDelete); if (isLikeDeleted) { - this.likeCount--; - atomicLikeCount.decrementAndGet(); + likeCount.decrementAndGet(); return true; } return false; @@ -196,7 +187,7 @@ public int getLength() { } public int getLikeCount() { - return likeCount; + return likeCount.get(); } public void setSong(final Song song) { diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java b/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java new file mode 100644 index 00000000..26a2105d --- /dev/null +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java @@ -0,0 +1,19 @@ +package shook.shook.song.domain.killingpart; + +import jakarta.persistence.AttributeConverter; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.stereotype.Component; + +@Component +public class LikeCountConverter implements AttributeConverter { + + @Override + public Integer convertToDatabaseColumn(final AtomicInteger attribute) { + return attribute.get(); + } + + @Override + public AtomicInteger convertToEntityAttribute(final Integer dbData) { + return new AtomicInteger(dbData); + } +} diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java index e4bb18aa..4554fe99 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java @@ -102,7 +102,7 @@ void likeByMultiplePeople() throws InterruptedException { final KillingPart killingPart = inMemorySongs.getSongById(SAVED_SONG.getId()).getKillingParts().stream() .filter(kp -> kp.getId().equals(SAVED_KILLING_PART.getId())) .findAny().get(); - assertThat(killingPart.getAtomicLikeCount().get()).isEqualTo(2); + assertThat(killingPart.getLikeCount()).isEqualTo(2); } @Disabled("UPDATE + 1 사용 시 한 사용자의 동시에 도착하는 좋아요 요청 동시성 문제 발생") From 42665419b0111f5971e41c101d3dbf2c98f16186 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:52:14 +0900 Subject: [PATCH 11/19] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20dto=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/SongWithKillingPartsAndLikes.java | 22 ------------ .../KillingPartLikeConcurrencyTest.java | 36 ++++++++----------- 2 files changed, 15 insertions(+), 43 deletions(-) delete mode 100644 backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java diff --git a/backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java b/backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java deleted file mode 100644 index 465e57f0..00000000 --- a/backend/src/main/java/shook/shook/song/domain/repository/dto/SongWithKillingPartsAndLikes.java +++ /dev/null @@ -1,22 +0,0 @@ -package shook.shook.song.domain.repository.dto; - -import java.util.List; -import shook.shook.song.domain.Song; -import shook.shook.song.domain.killingpart.KillingPart; -import shook.shook.song.domain.killingpart.KillingPartLike; - -public interface SongWithKillingPartsAndLikes { - - Song getSong(); - - default List getKillingPart() { - return getSong().getKillingParts(); - } - - default List getKillingPartLikes() { - return getKillingPart().stream() - .map(KillingPart::getKillingPartLikes) - .flatMap(List::stream) - .toList(); - } -} diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java index 4554fe99..5eef80e2 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeConcurrencyTest.java @@ -119,29 +119,23 @@ void likeByOnePersonMultipleTimes() throws InterruptedException { final KillingPartLikeRequest likeRequest = new KillingPartLikeRequest(true); final KillingPartLikeRequest unlikeRequest = new KillingPartLikeRequest(false); - executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), - likeRequest); - latch.countDown(); - return null; - })) + executorService.execute(() -> transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); + latch.countDown(); + return null; + })) ); - executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), - unlikeRequest); - latch.countDown(); - return null; - })) + executorService.execute(() -> transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), unlikeRequest); + latch.countDown(); + return null; + })) ); - executorService.execute(() -> - transactionTemplate.execute((status -> { - likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), - likeRequest); - latch.countDown(); - return null; - })) + executorService.execute(() -> transactionTemplate.execute((status -> { + likeService.updateLikeStatus(SAVED_KILLING_PART.getId(), first.getId(), likeRequest); + latch.countDown(); + return null; + })) ); latch.await(); From 23737908ff117c2fe97051e6e06f49fe3b5588d5 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:53:36 +0900 Subject: [PATCH 12/19] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/shook/song/domain/repository/SongRepository.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java b/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java index 542988ec..1fed695f 100644 --- a/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java +++ b/backend/src/main/java/shook/shook/song/domain/repository/SongRepository.java @@ -19,12 +19,6 @@ public interface SongRepository extends JpaRepository { + "GROUP BY s.id") List findAllWithTotalLikeCount(); - @Query("SELECT s AS song " - + "FROM Song s " - + "LEFT JOIN FETCH s.killingParts.killingParts kp " - + "GROUP BY s.id, kp.id") - List findAllWithKillingParts(); - @Query("SELECT s AS song " + "FROM Song s " + "LEFT JOIN FETCH s.killingParts.killingParts kp " From 1d3fdcc4f68e908bbac6f28329611e429ef5d19f Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 16:59:39 +0900 Subject: [PATCH 13/19] =?UTF-8?q?refactor:=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=9E=90=20private=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/shook/shook/song/domain/InMemorySongs.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index a10017f7..3a3da5b8 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -47,7 +47,7 @@ public List getSongs(final int limit) { .toList(); } - public List getSortedSongsByGenre(final Genre genre) { + private List getSortedSongsByGenre(final Genre genre) { return sortedSongIds.stream() .map(songs::get) .filter(song -> song.getGenre() == genre) From 43c5651040fdc31815181636de92bf7d0e419dd7 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:00:41 +0900 Subject: [PATCH 14/19] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EB=B2=94=EC=9C=84=20=EA=B2=80=EC=A6=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/shook/shook/song/domain/InMemorySongs.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index 3a3da5b8..e233f96a 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -142,25 +142,25 @@ private void reorder(final Song updatedSong) { } private boolean shouldMoveForward(final Song song, final int index) { - if (index == 0) { + if (index <= 0) { return false; } final Long prevSongId = sortedSongIds.get(index - 1); final Song prevSong = songs.get(prevSongId); - return index > 0 && shouldSwapWithPrevious(song, prevSong); + return shouldSwapWithPrevious(song, prevSong); } private boolean shouldMoveBackward(final Song song, final int index) { - if (index == sortedSongIds.size() - 1) { + if (index >= sortedSongIds.size() - 1) { return false; } final Long nextSongId = sortedSongIds.get(index + 1); final Song nextSong = songs.get(nextSongId); - return index < sortedSongIds.size() - 1 && shouldSwapWithNext(song, nextSong); + return shouldSwapWithNext(song, nextSong); } private void moveForward(final Song changedSong, final int songIndex) { From e70081159655028507ad0d6b20e47a069c01741a Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Sat, 4 Nov 2023 17:04:23 +0900 Subject: [PATCH 15/19] =?UTF-8?q?refactor:=20=EB=B6=84=EA=B8=B0=EB=AC=B8?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/song/domain/InMemorySongs.java | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index e233f96a..a3783b7a 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -132,35 +132,8 @@ private void reorder(final Song updatedSong) { return; } - if (shouldMoveForward(updatedSong, currentSongIndex)) { - moveForward(updatedSong, currentSongIndex); - } - - if (shouldMoveBackward(updatedSong, currentSongIndex)) { - moveBackward(updatedSong, currentSongIndex); - } - } - - private boolean shouldMoveForward(final Song song, final int index) { - if (index <= 0) { - return false; - } - - final Long prevSongId = sortedSongIds.get(index - 1); - final Song prevSong = songs.get(prevSongId); - - return shouldSwapWithPrevious(song, prevSong); - } - - private boolean shouldMoveBackward(final Song song, final int index) { - if (index >= sortedSongIds.size() - 1) { - return false; - } - - final Long nextSongId = sortedSongIds.get(index + 1); - final Song nextSong = songs.get(nextSongId); - - return shouldSwapWithNext(song, nextSong); + moveForward(updatedSong, currentSongIndex); + moveBackward(updatedSong, currentSongIndex); } private void moveForward(final Song changedSong, final int songIndex) { From 2e8398a2ae409f2f75c3206ea1568b85a0536fce Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:48:35 +0900 Subject: [PATCH 16/19] =?UTF-8?q?feat:=20likeCount=20AtomicInteger=20?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EA=B0=9C=EC=88=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=B2=A0=EC=9D=B4=EC=8A=A4=EC=99=80=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/InMemorySongsScheduler.java | 11 +++ .../shook/song/application/SongService.java | 1 - .../killingpart/KillingPartLikeService.java | 1 + .../shook/shook/song/domain/KillingParts.java | 2 +- .../killingpart/LikeCountConverter.java | 4 +- .../repository/KillingPartRepository.java | 5 +- .../src/main/resources/application-test.yml | 1 + backend/src/main/resources/application.yml | 1 + backend/src/main/resources/shook-security | 2 +- .../InMemorySongsSchedulerTest.java | 36 +++++++++ .../KillingPartLikeServiceTest.java | 2 + .../repository/KillingPartRepositoryTest.java | 74 ++++++++++--------- .../shook/shook/support/AcceptanceTest.java | 4 +- 13 files changed, 98 insertions(+), 46 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java index 323f95d9..67c9344e 100644 --- a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java +++ b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java @@ -10,6 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import shook.shook.song.domain.InMemorySongs; import shook.shook.song.domain.Song; +import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.repository.SongRepository; @RequiredArgsConstructor @@ -43,4 +44,14 @@ private void detachSongs(final List songs) { .flatMap(killingPart -> killingPart.getKillingPartLikes().stream()) .forEach(entityManager::detach); } + + @Transactional + @Scheduled(cron = "${schedules.in-memory-song.update-cron}") + public void updateCachedSong() { + log.info("InMemorySongsScheduler LikeCount update progressed"); + final List killingParts = inMemorySongs.getSongs().stream() + .flatMap(song -> song.getKillingParts().stream()) + .toList(); + killingParts.forEach(entityManager::merge); + } } diff --git a/backend/src/main/java/shook/shook/song/application/SongService.java b/backend/src/main/java/shook/shook/song/application/SongService.java index 8f417a8a..ba86c3b1 100644 --- a/backend/src/main/java/shook/shook/song/application/SongService.java +++ b/backend/src/main/java/shook/shook/song/application/SongService.java @@ -14,7 +14,6 @@ import shook.shook.member.domain.Member; import shook.shook.member.domain.repository.MemberRepository; import shook.shook.member.exception.MemberException; -import shook.shook.song.application.dto.RecentSongCarouselResponse; import shook.shook.member_part.domain.MemberPart; import shook.shook.member_part.domain.repository.MemberPartRepository; import shook.shook.song.application.dto.RecentSongCarouselResponse; diff --git a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java index dadc3177..2399e648 100644 --- a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java +++ b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java @@ -70,6 +70,7 @@ private void delete(final KillingPart killingPart, final Member member) { killingPart.findLikeByMember(member) .ifPresent(likeOnKillingPart -> { inMemorySongs.unlike(killingPart, likeOnKillingPart); + likeRepository.cancelLike(likeOnKillingPart.getId()); }); } } diff --git a/backend/src/main/java/shook/shook/song/domain/KillingParts.java b/backend/src/main/java/shook/shook/song/domain/KillingParts.java index 1a4f8166..9957f7e1 100644 --- a/backend/src/main/java/shook/shook/song/domain/KillingParts.java +++ b/backend/src/main/java/shook/shook/song/domain/KillingParts.java @@ -64,7 +64,7 @@ public List getKillingPartsSortedByLikeCount() { public int getKillingPartsTotalLikeCount() { return killingParts.stream() - .mapToInt(killingPart -> killingPart.getAtomicLikeCount().get()) + .mapToInt(KillingPart::getLikeCount) .reduce(0, Integer::sum); } } diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java b/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java index 26a2105d..303494a1 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/LikeCountConverter.java @@ -1,10 +1,10 @@ package shook.shook.song.domain.killingpart; import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.stereotype.Component; -@Component +@Converter public class LikeCountConverter implements AttributeConverter { @Override diff --git a/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java b/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java index 99679d46..17b8754d 100644 --- a/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java +++ b/backend/src/main/java/shook/shook/song/domain/killingpart/repository/KillingPartRepository.java @@ -2,9 +2,6 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; @@ -14,6 +11,7 @@ public interface KillingPartRepository extends JpaRepository List findAllBySong(final Song song); + /* AtomicInteger 사용하면서 예외 발생하여 주석 처리 @Query("update KillingPart kp set kp.likeCount = kp.likeCount + 1 where kp.id = :id") @Modifying(clearAutomatically = true, flushAutomatically = true) void increaseLikeCount(@Param("id") final Long killingPartLikeId); @@ -21,4 +19,5 @@ public interface KillingPartRepository extends JpaRepository @Query("update KillingPart kp set kp.likeCount = kp.likeCount - 1 where kp.id = :id") @Modifying(clearAutomatically = true, flushAutomatically = true) void decreaseLikeCount(@Param("id") final Long killingPartLikeId); + */ } diff --git a/backend/src/main/resources/application-test.yml b/backend/src/main/resources/application-test.yml index baa73d9c..efda546b 100644 --- a/backend/src/main/resources/application-test.yml +++ b/backend/src/main/resources/application-test.yml @@ -51,3 +51,4 @@ schedules: cron: "0/1 * * * * *" in-memory-song: cron: "0 0/5 * * * *" # 1분 + update-cron: "0 0/5 * * * *" # 1분 diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index c3334135..eb64e781 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -70,3 +70,4 @@ schedules: cron: "0 0 0/1 * * *" in-memory-song: cron: "0 0 0/1 * * *" #1시간 + update-cron: "0 0 0/1 * * *" #1시간 diff --git a/backend/src/main/resources/shook-security b/backend/src/main/resources/shook-security index be8515fd..48e7440b 160000 --- a/backend/src/main/resources/shook-security +++ b/backend/src/main/resources/shook-security @@ -1 +1 @@ -Subproject commit be8515fd110a4496fcce0c79c8fc1e312c49f6bc +Subproject commit 48e7440b247f24098ccad67de621e950c11df33c diff --git a/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java b/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java index cbe6ea25..56bad1a0 100644 --- a/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java +++ b/backend/src/test/java/shook/shook/song/application/InMemorySongsSchedulerTest.java @@ -8,7 +8,14 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.test.context.jdbc.Sql; +import shook.shook.member.domain.Member; +import shook.shook.member.domain.repository.MemberRepository; import shook.shook.song.domain.InMemorySongs; +import shook.shook.song.domain.Song; +import shook.shook.song.domain.killingpart.KillingPart; +import shook.shook.song.domain.killingpart.KillingPartLike; +import shook.shook.song.domain.killingpart.repository.KillingPartLikeRepository; +import shook.shook.song.domain.killingpart.repository.KillingPartRepository; @Sql(value = "classpath:/killingpart/initialize_killing_part_song.sql") @EnableScheduling @@ -21,6 +28,15 @@ class InMemorySongsSchedulerTest { @Autowired private InMemorySongsScheduler scheduler; + @Autowired + private KillingPartLikeRepository likeRepository; + + @Autowired + private KillingPartRepository killingPartRepository; + + @Autowired + private MemberRepository memberRepository; + @DisplayName("InMemorySongs 를 재생성한다.") @Test void recreateCachedSong() { @@ -31,4 +47,24 @@ void recreateCachedSong() { // then assertThat(inMemorySongs.getSongs()).hasSize(4); } + + @DisplayName("InMemorySongs 의 상태로 데이터베이스를 업데이트한다.") + @Test + void updateCachedSong() { + // given + scheduler.recreateCachedSong(); + final Song song = inMemorySongs.getSongById(1L); + final KillingPart killingPart = song.getKillingParts().get(0); + final Member member = memberRepository.save(new Member("email@email.com", "nickname")); + inMemorySongs.like(killingPart, likeRepository.save( + new KillingPartLike(killingPart, member) + )); + + // when + scheduler.updateCachedSong(); + + // then + killingPartRepository.findById(killingPart.getId()) + .ifPresent(updatedKillingPart -> assertThat(updatedKillingPart.getLikeCount()).isEqualTo(1)); + } } diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java index 18c6284c..9cba05fd 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java @@ -5,6 +5,7 @@ import java.util.Optional; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -232,6 +233,7 @@ void delete_alreadyDeleted_noAction() { .hasFieldOrPropertyWithValue("likeCount", 0); } + @Disabled() @DisplayName("좋아요 데이터가 존재하는 경우, 상태가 변경된다.") @Test void create_noAction() { diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java index 19299528..65b1b476 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java @@ -6,6 +6,7 @@ import java.time.temporal.ChronoUnit; import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,7 @@ import shook.shook.song.domain.repository.SongRepository; import shook.shook.support.UsingJpaTest; +@Disabled class KillingPartRepositoryTest extends UsingJpaTest { private static Song SAVED_SONG; @@ -110,40 +112,40 @@ void findAllBySong() { ); } - @DisplayName("한 킬링파트에 UPDATE + 1로 좋아요 수를 증가시킨다.") - @Test - void increaseLikeCount() { - // given - killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); - final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); - final int initialLikeCount = killingPart.getLikeCount(); - - // when - saveAndClearEntityManager(); - killingPartRepository.increaseLikeCount(killingPart.getId()); - - // then - final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); - - assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount + 1); - } - - @DisplayName("한 킬링파트에 UPDATE - 1로 좋아요 수를 감소시킨다.") - @Test - void decreaseLikeCount() { - // given - killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); - killingPartRepository.increaseLikeCount(FIRST_KILLING_PART.getId()); - final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); - final int initialLikeCount = killingPart.getLikeCount(); - - // when - saveAndClearEntityManager(); - killingPartRepository.decreaseLikeCount(killingPart.getId()); - - // then - final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); - - assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount - 1); - } +// @DisplayName("한 킬링파트에 UPDATE + 1로 좋아요 수를 증가시킨다.") +// @Test +// void increaseLikeCount() { +// // given +// killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); +// final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); +// final int initialLikeCount = killingPart.getLikeCount(); +// +// // when +// saveAndClearEntityManager(); +// killingPartRepository.increaseLikeCount(killingPart.getId()); +// +// // then +// final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); +// +// assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount + 1); +// } +// +// @DisplayName("한 킬링파트에 UPDATE - 1로 좋아요 수를 감소시킨다.") +// @Test +// void decreaseLikeCount() { +// // given +// killingPartRepository.saveAll(KILLING_PARTS.getKillingParts()); +// killingPartRepository.increaseLikeCount(FIRST_KILLING_PART.getId()); +// final KillingPart killingPart = killingPartRepository.findById(FIRST_KILLING_PART.getId()).get(); +// final int initialLikeCount = killingPart.getLikeCount(); +// +// // when +// saveAndClearEntityManager(); +// killingPartRepository.decreaseLikeCount(killingPart.getId()); +// +// // then +// final KillingPart foundKillingPart = killingPartRepository.findById(killingPart.getId()).get(); +// +// assertThat(foundKillingPart.getLikeCount()).isEqualTo(initialLikeCount - 1); +// } } diff --git a/backend/src/test/java/shook/shook/support/AcceptanceTest.java b/backend/src/test/java/shook/shook/support/AcceptanceTest.java index 7e7791d3..d1a1bb51 100644 --- a/backend/src/test/java/shook/shook/support/AcceptanceTest.java +++ b/backend/src/test/java/shook/shook/support/AcceptanceTest.java @@ -1,7 +1,7 @@ package shook.shook.support; import io.restassured.RestAssured; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -16,7 +16,7 @@ public class AcceptanceTest { @LocalServerPort private int port; - @BeforeEach + @AfterEach void setUp() { RestAssured.port = port; dataCleaner.clear(); From 03654ec920db05ca464518c56aa99cad1f5dcbe4 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Mon, 20 Nov 2023 17:51:44 +0900 Subject: [PATCH 17/19] =?UTF-8?q?fix:=20setUp=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=8B=A4=ED=96=89=20=EC=8B=9C=EC=A0=90=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/test/java/shook/shook/support/AcceptanceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/shook/shook/support/AcceptanceTest.java b/backend/src/test/java/shook/shook/support/AcceptanceTest.java index d1a1bb51..7e7791d3 100644 --- a/backend/src/test/java/shook/shook/support/AcceptanceTest.java +++ b/backend/src/test/java/shook/shook/support/AcceptanceTest.java @@ -1,7 +1,7 @@ package shook.shook.support; import io.restassured.RestAssured; -import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -16,7 +16,7 @@ public class AcceptanceTest { @LocalServerPort private int port; - @AfterEach + @BeforeEach void setUp() { RestAssured.port = port; dataCleaner.clear(); From dc735631f84a8266faf2f520bf1b6b1f0d2a8999 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:38:32 +0900 Subject: [PATCH 18/19] =?UTF-8?q?refactor:=20synchronized=20=EB=A1=9C=20?= =?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=84=B1=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/InMemorySongsScheduler.java | 1 + .../killingpart/KillingPartLikeService.java | 1 + .../shook/song/domain/InMemorySongs.java | 36 ++++++++++--------- .../KillingPartLikeServiceTest.java | 17 +++------ .../shook/song/domain/InMemorySongsTest.java | 4 --- .../shook/song/ui/MyPageControllerTest.java | 6 ++++ 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java index 67c9344e..41355176 100644 --- a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java +++ b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java @@ -53,5 +53,6 @@ public void updateCachedSong() { .flatMap(song -> song.getKillingParts().stream()) .toList(); killingParts.forEach(entityManager::merge); + recreateCachedSong(); } } diff --git a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java index 2399e648..0146323d 100644 --- a/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java +++ b/backend/src/main/java/shook/shook/song/application/killingpart/KillingPartLikeService.java @@ -57,6 +57,7 @@ private void create(final KillingPart killingPart, final Member member) { .orElseGet(() -> createNewLike(killingPart, member)); if (likeOnKillingPart.isDeleted()) { inMemorySongs.like(killingPart, likeOnKillingPart); + likeRepository.pressLike(likeOnKillingPart.getId()); } } diff --git a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java index a3783b7a..83d69779 100644 --- a/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java +++ b/backend/src/main/java/shook/shook/song/domain/InMemorySongs.java @@ -25,7 +25,7 @@ public class InMemorySongs { private Map songs = new HashMap<>(); private List sortedSongIds = new ArrayList<>(); - public void refreshSongs(final List songs) { + public synchronized void refreshSongs(final List songs) { this.songs = songs.stream() .collect(Collectors.toMap(Song::getId, song -> song, (prev, update) -> update, HashMap::new)); this.sortedSongIds = new ArrayList<>(this.songs.keySet().stream() @@ -116,7 +116,16 @@ public void like(final KillingPart killingPart, final KillingPartLike likeOnKill } } - private static KillingPart findKillingPart(final KillingPart killingPart, final Song song) { + public void unlike(final KillingPart killingPart, final KillingPartLike unlikeOnKillingPart) { + final Song song = songs.get(killingPart.getSong().getId()); + final KillingPart killingPartById = findKillingPart(killingPart, song); + final boolean updated = killingPartById.unlike(unlikeOnKillingPart); + if (updated) { + reorder(song); + } + } + + private KillingPart findKillingPart(final KillingPart killingPart, final Song song) { return song.getKillingParts().stream() .filter(kp -> kp.equals(killingPart)) .findAny() @@ -126,14 +135,16 @@ private static KillingPart findKillingPart(final KillingPart killingPart, final } private void reorder(final Song updatedSong) { - int currentSongIndex = sortedSongIds.indexOf(updatedSong.getId()); + synchronized (sortedSongIds) { + int currentSongIndex = sortedSongIds.indexOf(updatedSong.getId()); - if (currentSongIndex == -1) { - return; - } + if (currentSongIndex == -1) { + return; + } - moveForward(updatedSong, currentSongIndex); - moveBackward(updatedSong, currentSongIndex); + moveForward(updatedSong, currentSongIndex); + moveBackward(updatedSong, currentSongIndex); + } } private void moveForward(final Song changedSong, final int songIndex) { @@ -186,13 +197,4 @@ private boolean shouldSwapWithNext(final Song song, final Song nextSong) { return hasSmallerTotalLikeCountThanNextSong || hasSameTotalLikeCountAndSmallerIdThanNextSong; } - - public void unlike(final KillingPart killingPart, final KillingPartLike unlikeOnKillingPart) { - final Song song = songs.get(killingPart.getSong().getId()); - final KillingPart killingPartById = findKillingPart(killingPart, song); - final boolean updated = killingPartById.unlike(unlikeOnKillingPart); - if (updated) { - reorder(song); - } - } } diff --git a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java index 9cba05fd..f3fd844b 100644 --- a/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java +++ b/backend/src/test/java/shook/shook/song/application/killingpart/KillingPartLikeServiceTest.java @@ -5,7 +5,6 @@ import java.util.Optional; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -47,6 +46,7 @@ class KillingPartLikeServiceTest extends UsingJpaTest { private SongRepository songRepository; private KillingPartLikeService likeService; + private InMemorySongs inMemorySongs; @BeforeEach @@ -221,19 +221,15 @@ void delete_alreadyDeleted_noAction() { // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Song savedSong = inMemorySongs.getSongById(SAVED_SONG.getId()); assertThat(savedLike).isPresent() .get() .hasFieldOrPropertyWithValue("isDeleted", true); - assertThat(updatedKillingPart).isPresent() - .get() - .hasFieldOrPropertyWithValue("likeCount", 0); + assertThat(savedSong.getTotalLikeCount()).isZero(); } - @Disabled() @DisplayName("좋아요 데이터가 존재하는 경우, 상태가 변경된다.") @Test void create_noAction() { @@ -251,16 +247,13 @@ void create_noAction() { // then final Optional savedLike = killingPartLikeRepository. findByKillingPartAndMember(SAVED_KILLING_PART, SAVED_MEMBER); - final Optional updatedKillingPart = killingPartRepository.findById( - SAVED_KILLING_PART.getId()); + final Song savedSong = inMemorySongs.getSongById(SAVED_SONG.getId()); assertThat(savedLike).isPresent() .get() .hasFieldOrPropertyWithValue("isDeleted", true); - assertThat(updatedKillingPart).isPresent() - .get() - .hasFieldOrPropertyWithValue("likeCount", 0); + assertThat(savedSong.getTotalLikeCount()).isZero(); } @DisplayName("존재하지 않는 킬링파트면 예외가 발생한다.") diff --git a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java index e849e27a..5f6abd39 100644 --- a/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java +++ b/backend/src/test/java/shook/shook/song/domain/InMemorySongsTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import jakarta.persistence.EntityManager; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -30,9 +29,6 @@ class InMemorySongsTest extends UsingJpaTest { @Autowired private MemberRepository memberRepository; - @Autowired - private EntityManager entityManager; - @BeforeEach void setUp() { MEMBER = memberRepository.findById(1L).get(); diff --git a/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java b/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java index 9016cf81..73392533 100644 --- a/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java +++ b/backend/src/test/java/shook/shook/song/ui/MyPageControllerTest.java @@ -25,6 +25,7 @@ import shook.shook.song.application.dto.MyPartsResponse; import shook.shook.song.application.killingpart.KillingPartLikeService; import shook.shook.song.application.killingpart.dto.KillingPartLikeRequest; +import shook.shook.song.domain.InMemorySongs; import shook.shook.song.domain.Song; import shook.shook.song.domain.killingpart.KillingPart; import shook.shook.song.domain.killingpart.repository.KillingPartRepository; @@ -46,6 +47,9 @@ void setUp() { private static final long SAVED_MEMBER_ID = 1L; private static final String SAVED_MEMBER_NICKNAME = "nickname"; + @Autowired + private InMemorySongs inMemorySongs; + @Autowired private TokenProvider tokenProvider; @@ -73,6 +77,8 @@ class GetLikedKillingParts { @Test void likedKillingPartExistWithOneDeletedLikeExist() { //given + inMemorySongs.refreshSongs(songRepository.findAllWithKillingPartsAndLikes()); + final String accessToken = tokenProvider.createAccessToken(SAVED_MEMBER_ID, SAVED_MEMBER_NICKNAME); From 358254eb5f99689e86cba388d2330533d6b5a857 Mon Sep 17 00:00:00 2001 From: Eunsol Kim <61370551+Cyma-s@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:47:27 +0900 Subject: [PATCH 19/19] =?UTF-8?q?fix:=20cache=20=EC=9E=AC=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shook/shook/song/application/InMemorySongsScheduler.java | 3 --- .../killingpart/repository/KillingPartRepositoryTest.java | 2 -- 2 files changed, 5 deletions(-) diff --git a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java index 41355176..d0035607 100644 --- a/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java +++ b/backend/src/main/java/shook/shook/song/application/InMemorySongsScheduler.java @@ -40,8 +40,6 @@ private void detachSongs(final List songs) { songs.stream() .peek(entityManager::detach) .flatMap(song -> song.getKillingParts().stream()) - .peek(entityManager::detach) - .flatMap(killingPart -> killingPart.getKillingPartLikes().stream()) .forEach(entityManager::detach); } @@ -53,6 +51,5 @@ public void updateCachedSong() { .flatMap(song -> song.getKillingParts().stream()) .toList(); killingParts.forEach(entityManager::merge); - recreateCachedSong(); } } diff --git a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java index 65b1b476..70e4db6f 100644 --- a/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java +++ b/backend/src/test/java/shook/shook/song/domain/killingpart/repository/KillingPartRepositoryTest.java @@ -6,7 +6,6 @@ import java.time.temporal.ChronoUnit; import java.util.List; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,7 +18,6 @@ import shook.shook.song.domain.repository.SongRepository; import shook.shook.support.UsingJpaTest; -@Disabled class KillingPartRepositoryTest extends UsingJpaTest { private static Song SAVED_SONG;