Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

[BE] feat: 복합 유니크 인덱스를 이용한 사용자의 중복실행(글,카테고리 추가) 막기 #417

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.donggle.backend.application.repository;

import org.donggle.backend.domain.writing.Writing;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -14,15 +13,15 @@ public interface WritingRepository extends JpaRepository<Writing, Long> {

int countByCategoryId(Long id);

int countByNextWritingId(Long nextWritingId);
int countByNextId(Long nextWritingId);

@Query("select w from Writing w " +
"where w.category.id = :categoryId and " +
"w.nextWriting is null")
"w.nextId = -1")
Optional<Writing> findLastWritingByCategoryId(@Param("categoryId") final Long categoryId);

@Query("select w from Writing w " +
"where w.nextWriting.id = :writingId")
"where w.nextId = :writingId")
Optional<Writing> findPreWritingByWritingId(@Param("writingId") final Long writingId);

@Query("select w from Writing w join fetch w.blocks where w.id = :writingId")
Expand All @@ -33,6 +32,11 @@ public interface WritingRepository extends JpaRepository<Writing, Long> {
"w.status = 'TRASHED'", nativeQuery = true)
List<Writing> findAllByMemberIdAndStatusIsTrashed(@Param("memberId") final Long memberId);

@Query(value = "select * from writing w " +
"where w.member_id = :memberId and " +
"w.status = 'TRASHED' IN (:writingIds)", nativeQuery = true)
List<Writing> findTrashedWritingsByIds(@Param("memberId") final Long memberId, @Param("writingIds") final List<Long> writingIds);

@Query(value = "select * from writing w " +
"where w.member_id = :memberId and " +
"w.category_id = :categoryId and " +
Expand All @@ -50,4 +54,9 @@ public interface WritingRepository extends JpaRepository<Writing, Long> {
"w.id = :writingId and " +
"w.status != 'DELETED'", nativeQuery = true)
Optional<Writing> findByMemberIdAndWritingIdAndStatusIsNotDeleted(@Param("memberId") final Long memberId, @Param("writingId") final Long writingId);

@Query(value = "update writing " +
"set next_writing_id = newWritingId " +
"where id = lastWritingId ", nativeQuery = true)
void updateNextId(Long lastWritingId, Long newWritingId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.donggle.backend.application.repository.WritingRepository;
import org.donggle.backend.application.service.request.CategoryAddRequest;
import org.donggle.backend.application.service.request.CategoryModifyRequest;
import org.donggle.backend.domain.OrderStatus;
import org.donggle.backend.domain.category.Category;
import org.donggle.backend.domain.category.CategoryName;
import org.donggle.backend.domain.member.Member;
Expand All @@ -29,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@Transactional
Expand Down Expand Up @@ -97,38 +99,45 @@ public CategoryWritingsResponse findAllWritings(final Long memberId, final Long
if (findWritings.isEmpty()) {
return CategoryWritingsResponse.of(findCategory, Collections.emptyList());
}
final Writing firstWriting = findFirstWriting(findWritings);
final List<Writing> sortedWriting = sortWriting(findWritings, firstWriting);
final List<WritingSimpleResponse> writingSimpleResponses = sortedWriting.stream()
final Long firstWritingId = findFirstWritingId(findWritings);
final List<Long> sortWritingIds = sortWriting(findWritings, firstWritingId);
final Map<Long, Writing> wiritingMap = findWritings.stream()
.collect(Collectors.toMap(Writing::getId, writing -> writing));
final List<Writing> sortWriting = sortWritingIds.stream()
.map(wiritingMap::get)
.toList();
final List<WritingSimpleResponse> writingSimpleResponses = sortWriting.stream()
.map(WritingSimpleResponse::from)
.toList();
return CategoryWritingsResponse.of(findCategory, writingSimpleResponses);
}

private Writing findFirstWriting(final List<Writing> findWritings) {
final List<Writing> copy = new ArrayList<>(findWritings);
final List<Writing> nextWritings = findWritings.stream()
.map(Writing::getNextWriting)
private List<Long> sortWriting(final List<Writing> writings, final Long firstWritingId) {
final Map<Long, Long> writingMap = new LinkedHashMap<>();
for (final Writing writing : writings) {
writingMap.put(writing.getId(), writing.getNextId());
}
final List<Long> sortedWritingIds = new ArrayList<>();
sortedWritingIds.add(firstWritingId);
Long targetWritingId = firstWritingId;
while (!Objects.equals(writingMap.get(targetWritingId), OrderStatus.END.getStatusValue())) {
targetWritingId = writingMap.get(targetWritingId);
sortedWritingIds.add(targetWritingId);
}
return sortedWritingIds;
}

private Long findFirstWritingId(final List<Writing> findWritings) {
final List<Long> copy = new ArrayList<>(findWritings.stream()
.map(Writing::getId)
.toList());
final List<Long> nextWritings = findWritings.stream()
.map(Writing::getNextId)
.toList();
copy.removeAll(nextWritings);
return copy.get(FIRST_WRITING_INDEX);
}

private List<Writing> sortWriting(final List<Writing> findWritings, final Writing firstWriting) {
final Map<Writing, Writing> writingMap = new LinkedHashMap<>();
for (final Writing findWriting : findWritings) {
writingMap.put(findWriting, findWriting.getNextWriting());
}
final List<Writing> sortedWriting = new ArrayList<>();
sortedWriting.add(firstWriting);
Writing targetWriting = firstWriting;
while (Objects.nonNull(targetWriting.getNextWriting())) {
targetWriting = writingMap.get(targetWriting);
sortedWriting.add(targetWriting);
}
return sortedWriting;
}

public void modifyCategoryName(final Long memberId, final Long categoryId, final CategoryModifyRequest request) {
final Member findMember = findMember(memberId);
final Category findCategory = findCategory(findMember.getId(), categoryId);
Expand Down Expand Up @@ -156,10 +165,10 @@ public void removeCategory(final Long memberId, final Long categoryId) {
private void transferToBasicCategory(final Category basicCategory, final Category findCategory) {
if (haveWritingsCategory(findCategory)) {
final List<Writing> findWritings = writingRepository.findAllByCategoryId(findCategory.getId());
final Writing firstWritingInCategory = findFirstWriting(findWritings);
final Long firstWritingId = findFirstWritingId(findWritings);
if (haveWritingsCategory(basicCategory)) {
final Writing lastWritingInBasicCategory = findLastWritingInCategory(basicCategory);
lastWritingInBasicCategory.changeNextWriting(firstWritingInCategory);
lastWritingInBasicCategory.changeNextWritingId(firstWritingId);
}
findWritings.forEach(writing -> writing.changeCategory(basicCategory));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import lombok.RequiredArgsConstructor;
import org.donggle.backend.application.repository.WritingRepository;
import org.donggle.backend.domain.category.Category;
import org.donggle.backend.domain.writing.Writing;
import org.donggle.backend.exception.notfound.DeleteWritingNotFoundException;
import org.donggle.backend.exception.notfound.RestoreWritingNotFoundException;
import org.donggle.backend.exception.notfound.WritingNotFoundException;
import org.donggle.backend.ui.response.TrashResponse;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -33,36 +33,28 @@ public void trashWritings(final Long memberId, final List<Long> writingIds) {
return findWriting;
})
.forEach(writing -> {
final Writing nextWriting = writing.getNextWriting();
final Long nextWritingId = writing.getNextId();
writing.moveToTrash();
writingRepository.findPreWritingByWritingId(writing.getId())
.ifPresent(preWriting -> preWriting.changeNextWriting(nextWriting));
.ifPresent(preWriting -> preWriting.changeNextWritingId(nextWritingId));
});
}

public void deleteWritings(final Long memberId, final List<Long> writingIds) {
writingIds.stream()
.map(writingId -> writingRepository.findByMemberIdAndWritingIdAndStatusIsNotDeleted(memberId, writingId)
.orElseThrow(() -> new DeleteWritingNotFoundException(writingId)))
.forEach(writing -> {
final Writing nextWriting = writing.getNextWriting();
writing.changeNextWritingNull();
writingRepository.delete(writing);
writingRepository.findPreWritingByWritingId(writing.getId())
.ifPresent(preWriting -> preWriting.changeNextWriting(nextWriting));
});
final List<Writing> trashedWritings = writingRepository.findTrashedWritingsByIds(memberId, writingIds);
writingRepository.deleteAll(trashedWritings);
}

public void restoreWritings(final Long memberId, final List<Long> writingIds) {
writingIds.stream()
.map(writingId -> writingRepository.findByMemberIdAndWritingIdAndStatusIsTrashed(memberId, writingId)
.orElseThrow(() -> new RestoreWritingNotFoundException(writingId)))
.forEach(writing -> {
writingRepository.findLastWritingByCategoryId(writing.getCategory().getId())
.ifPresent(lastWriting -> lastWriting.changeNextWriting(writing));
writing.restore();
}
);
final List<Writing> trashedWritings = writingRepository.findTrashedWritingsByIds(memberId, writingIds);
for (final Writing trashedWriting : trashedWritings) {
final Category category = trashedWriting.getCategory();
if (writingRepository.findLastWritingByCategoryId(category.getId()).isPresent()) {
final Writing writing = writingRepository.findLastWritingByCategoryId(category.getId()).get();
writing.changeNextWritingId(trashedWriting.getId());
}
trashedWriting.restore();
}
}

private void validateAuthorization(final Long memberId, final Writing writing) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.donggle.backend.application.repository.MemberRepository;
import org.donggle.backend.application.repository.WritingRepository;
import org.donggle.backend.application.service.request.WritingModifyRequest;
import org.donggle.backend.domain.OrderStatus;
import org.donggle.backend.domain.blog.BlogWriting;
import org.donggle.backend.domain.category.Category;
import org.donggle.backend.domain.member.Member;
Expand All @@ -31,13 +32,15 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@Transactional
@RequiredArgsConstructor
public class WritingService {
private static final String MD_FORMAT = ".md";
private static final int LAST_WRITING_FLAG = -1;
private static final int FIRST_WRITING_INDEX = 0;

private final MemberRepository memberRepository;
private final WritingRepository writingRepository;
Expand Down Expand Up @@ -72,9 +75,11 @@ public Writing saveAndGetWriting(final Category findCategory, final Writing writ
if (isNotEmptyCategory(findCategory)) {
final Writing lastWriting = findLastWritingInCategory(findCategory.getId());
final Writing savedWriting = writingRepository.save(writing);
lastWriting.changeNextWriting(savedWriting);
lastWriting.changeNextWritingId(savedWriting.getId());
savedWriting.changeNextWritingId(OrderStatus.END.getStatusValue());
return savedWriting;
}
writing.changeNextWritingId(OrderStatus.END.getStatusValue());
return writingRepository.save(writing);
}

Expand Down Expand Up @@ -107,36 +112,43 @@ public WritingListWithCategoryResponse findWritingListByCategoryId(final Long me
if (findWritings.isEmpty()) {
return WritingListWithCategoryResponse.of(findCategory, Collections.emptyList());
}
final Writing firstWriting = findFirstWriting(findWritings);
final List<Writing> sortedWriting = sortWriting(findWritings, firstWriting);
final List<WritingDetailResponse> writingDetailResponses = sortedWriting.stream()
final Long firstWritingId = findFirstWritingId(findWritings);
final List<Long> sortWritingIds = sortWriting(findWritings, firstWritingId);
final Map<Long, Writing> wiritingMap = findWritings.stream()
.collect(Collectors.toMap(Writing::getId, writing -> writing));
final List<Writing> sortWriting = sortWritingIds.stream()
.map(wiritingMap::get)
.toList();
final List<WritingDetailResponse> writingDetailResponses = sortWriting.stream()
.map(writing -> WritingDetailResponse.of(writing, convertToPublishedDetailResponses(writing.getId())))
.toList();
return WritingListWithCategoryResponse.of(findCategory, writingDetailResponses);
}

private Writing findFirstWriting(final List<Writing> findWritings) {
final List<Writing> copy = new ArrayList<>(findWritings);
final List<Writing> nextWritings = findWritings.stream()
.map(Writing::getNextWriting)
private Long findFirstWritingId(final List<Writing> findWritings) {
final List<Long> copy = new ArrayList<>(findWritings.stream()
.map(Writing::getId)
.toList());
final List<Long> nextWritings = findWritings.stream()
.map(Writing::getNextId)
.toList();
copy.removeAll(nextWritings);
return copy.get(0);
return copy.get(FIRST_WRITING_INDEX);
}

private List<Writing> sortWriting(final List<Writing> writings, final Writing firstWriting) {
final Map<Writing, Writing> writingMap = new LinkedHashMap<>();
private List<Long> sortWriting(final List<Writing> writings, final Long firstWritingId) {
final Map<Long, Long> writingMap = new LinkedHashMap<>();
for (final Writing writing : writings) {
writingMap.put(writing, writing.getNextWriting());
writingMap.put(writing.getId(), writing.getNextId());
}
final List<Writing> sortedWritings = new ArrayList<>();
sortedWritings.add(firstWriting);
Writing targetWriting = firstWriting;
while (Objects.nonNull(targetWriting.getNextWriting())) {
targetWriting = writingMap.get(targetWriting);
sortedWritings.add(targetWriting);
final List<Long> sortedWritingIds = new ArrayList<>();
sortedWritingIds.add(firstWritingId);
Long targetWritingId = firstWritingId;
while (!Objects.equals(writingMap.get(targetWritingId), OrderStatus.END.getStatusValue())) {
targetWritingId = writingMap.get(targetWritingId);
sortedWritingIds.add(targetWritingId);
}
return sortedWritings;
return sortedWritingIds;
}

public void modifyWritingOrder(final Long memberId, final Long writingId, final WritingModifyRequest request) {
Expand All @@ -150,12 +162,12 @@ public void modifyWritingOrder(final Long memberId, final Long writingId, final
}

private void deleteWritingOrder(final Writing writing) {
final Writing nextWriting = writing.getNextWriting();
writing.changeNextWritingNull();
final Long nextWritingId = writing.getNextId();
writing.changeNextWritingId(null);

if (isNotFirstWriting(writing.getId())) {
final Writing preWriting = findPreWriting(writing.getId());
preWriting.changeNextWriting(nextWriting);
preWriting.changeNextWritingId(nextWritingId);
}
}

Expand All @@ -167,12 +179,12 @@ private void addWritingOrder(final Long memberId, final Long categoryId, final L
} else {
preWriting = findPreWriting(nextWritingId);
}
preWriting.changeNextWriting(writing);
preWriting.changeNextWritingId(writing.getId());
}
if (nextWritingId != LAST_WRITING_FLAG) {
final Writing nextWriting = findWritingById(nextWritingId);
validateAuthorization(memberId, nextWriting);
writing.changeNextWriting(nextWriting);
writing.changeNextWritingId(nextWritingId);
}
}

Expand All @@ -183,7 +195,7 @@ private void validateAuthorization(final Long memberId, final Writing writing) {
}

private boolean isNotFirstWriting(final Long writingId) {
return writingRepository.countByNextWritingId(writingId) != 0;
return writingRepository.countByNextId(writingId) != 0;
}

private void changeCategory(final Long memberId, final Long categoryId, final Writing writing) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.donggle.backend.application.repository.MemberCredentialsRepository;
import org.donggle.backend.application.repository.MemberRepository;
import org.donggle.backend.application.repository.WritingRepository;
import org.donggle.backend.domain.OrderStatus;
import org.donggle.backend.domain.blog.Blog;
import org.donggle.backend.domain.blog.BlogType;
import org.donggle.backend.domain.category.Category;
Expand Down Expand Up @@ -60,7 +61,7 @@ public void init() {
blogRepository.save(new Blog(BlogType.MEDIUM));
blogRepository.save(new Blog(BlogType.TISTORY));

writingRepository.save(Writing.of(
final Writing savedWriting = writingRepository.save(Writing.of(
savedMember,
new Title("테스트 글"),
savedCategory,
Expand All @@ -74,6 +75,7 @@ public void init() {
)
)
));
savedWriting.changeNextWritingId(OrderStatus.END.getStatusValue());
}
}
}
16 changes: 16 additions & 0 deletions backend/src/main/java/org/donggle/backend/domain/OrderStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.donggle.backend.domain;

public enum OrderStatus {
END(-1L),
DISCONNECTION(-2L);

private final Long statusValue;

OrderStatus(final Long statusValue) {
this.statusValue = statusValue;
}

public Long getStatusValue() {
return statusValue;
}
}
Loading