Skip to content

Commit

Permalink
fix: 삽입 정렬 로직 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
Cyma-s committed Oct 31, 2023
1 parent 46699a0 commit 9748b69
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 70 deletions.
101 changes: 90 additions & 11 deletions backend/src/main/java/shook/shook/song/domain/InMemorySongs.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
@Repository
public class InMemorySongs {

private static final Comparator<Song> COMPARATOR = Comparator.comparing(Song::getTotalLikeCount,
Comparator.reverseOrder())
.thenComparing(Song::getId, Comparator.reverseOrder());
private Map<Long, Song> songsSortedInLikeCountById;
private List<Long> sortedIds;
private static final Comparator<Song> COMPARATOR =
Comparator.comparing(Song::getTotalLikeCount, Comparator.reverseOrder())
.thenComparing(Song::getId, Comparator.reverseOrder());
private Map<Long, Song> songsSortedInLikeCountById = new HashMap<>();
private List<Long> sortedIds = new ArrayList<>();

private final EntityManager entityManager;

Expand All @@ -47,11 +47,13 @@ private static Map<Long, Song> getSortedSong(final List<Song> songs) {
}

public List<Song> getSongs() {
return songsSortedInLikeCountById.values().stream().toList();
return sortedIds.stream()
.map(songsSortedInLikeCountById::get)
.toList();
}

public List<Song> getSongs(final int limit) {
final List<Long> topSongIds = this.sortedIds.subList(0, Math.min(limit, this.sortedIds.size()));
final List<Long> topSongIds = sortedIds.subList(0, Math.min(limit, sortedIds.size()));

return topSongIds.stream()
.map(songsSortedInLikeCountById::get)
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -121,8 +118,8 @@ void findById_exist_login_member() {
//given
final Member member = createAndSaveMember("[email protected]", "email");
final Song song = registerNewSong("title");
inMemorySongs.recreate(List.of(song));
addLikeToEachKillingParts(song, member);
inMemorySongs.recreate(songRepository.findAllWithKillingParts());
addMemberPartToSong(10, 5, song, member);

//when
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -204,6 +201,7 @@ void findById_exist_not_login_member() {
void findById_notExist() {
//given
final Member member = createAndSaveMember("[email protected]", "email");
inMemorySongs.recreate(List.of());

//when
//then
Expand Down Expand Up @@ -231,8 +229,8 @@ void showHighLikedSongs() {
addLikeToEachKillingParts(thirdSong, member2);
addLikeToEachKillingParts(fourthSong, member1);

inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes());
saveAndClearEntityManager();
inMemorySongs.recreate(songRepository.findAllWithKillingParts());

// when
final List<HighLikedSongResponse> result = songService.showHighLikedSongs();
Expand Down Expand Up @@ -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);
Expand All @@ -317,6 +314,7 @@ void firstFindByMember() {
addMemberPartToSong(10, 5, fourthSong, member);

saveAndClearEntityManager();
inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes());

// when
final SongSwipeResponse result =
Expand Down Expand Up @@ -355,6 +353,8 @@ void firstFindByAnonymous() {
final Member member = createAndSaveMember("[email protected]", "first");
final Long notExistSongId = Long.MAX_VALUE;

saveAndClearEntityManager();

// when
// then
assertThatThrownBy(
Expand Down Expand Up @@ -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);
Expand All @@ -399,6 +397,7 @@ void findSongByIdForBeforeSwipe() {

// 정렬 순서: 2L, 4L, 1L, 5L, 3L
saveAndClearEntityManager();
inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes());

// when
final List<SongResponse> beforeResponses =
Expand Down Expand Up @@ -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);
Expand All @@ -441,8 +439,8 @@ void findSongByIdForAfterSwipe() {
addMemberPartToSong(10, 5, fifthSong, member);

// 정렬 순서: 2L, 4L, 1L, 5L, 3L

saveAndClearEntityManager();
inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes());

// when
final List<SongResponse> afterResponses =
Expand Down Expand Up @@ -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
Expand All @@ -508,9 +506,9 @@ void findSongById() {
final Song song = registerNewSong("title");
final Member member = createAndSaveMember("[email protected]", "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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@
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
class KillingPartLikeConcurrencyTest {

private static KillingPart SAVED_KILLING_PART;
private static Member SAVED_MEMBER;
private static Song SAVED_SONG;

@Autowired
private KillingPartRepository killingPartRepository;
Expand All @@ -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,
Expand All @@ -64,6 +71,7 @@ void likeByMultiplePeople() throws InterruptedException {
// given
final Member first = SAVED_MEMBER;
final Member second = memberRepository.save(new Member("[email protected]", "second"));
inMemorySongs.recreate(songRepository.findAllWithKillingPartsAndLikes());

// when
ExecutorService executorService = Executors.newFixedThreadPool(2);
Expand Down Expand Up @@ -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 사용 시 한 사용자의 동시에 도착하는 좋아요 요청 동시성 문제 발생")
Expand Down
Loading

0 comments on commit 9748b69

Please sign in to comment.