Skip to content
Open
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,6 +1,6 @@
package com.app.toaster.category.controller;

import com.app.toaster.category.controller.request.ChangeCateoryPriorityDto;
import com.app.toaster.category.controller.request.ChangeCategoryPriorityDto;
import com.app.toaster.category.controller.request.ChangeCateoryTitleDto;
import com.app.toaster.category.controller.request.CreateCategoryDto;
import com.app.toaster.common.dto.ApiResponse;
Expand Down Expand Up @@ -33,7 +33,7 @@ public class CategoryController {

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse createCateory(
public ApiResponse createCategory(
@UserId Long userId,
@Valid @RequestBody CreateCategoryDto createCategoryDto
){
Expand Down Expand Up @@ -61,9 +61,9 @@ public ApiResponse<CategoriesResponse> getCategories(@UserId Long userId){
@ResponseStatus(HttpStatus.OK)
public ApiResponse editCategoryPriority(
@UserId Long userId,
@RequestBody ChangeCateoryPriorityDto changeCateoryPriorityDto
@RequestBody ChangeCategoryPriorityDto changeCategoryPriorityDto
){
categoryService.editCategoryPriority(changeCateoryPriorityDto);
categoryService.editCategoryPriority(changeCategoryPriorityDto);
return ApiResponse.success(Success.UPDATE_CATEGORY_TITLE_SUCCESS);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import jakarta.validation.constraints.NotNull;

public record ChangeCateoryPriorityDto(
public record ChangeCategoryPriorityDto(
@NotNull
Long categoryId,
@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@
import org.springframework.data.jpa.repository.JpaRepository;

import com.app.toaster.category.domain.Category;

import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import jakarta.persistence.LockModeType;

public interface CategoryRepository extends JpaRepository<Category, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT c FROM Category c WHERE c.priority BETWEEN :minPriority AND :maxPriority")
List<Category> findAllByPriorityBetweenForUpdate(@Param("minPriority") int minPriority, @Param("maxPriority") int maxPriority);

@Query("SELECT COALESCE(MAX(c.priority), 0) FROM Category c WHERE c.user = :user")
int findMaxPriorityByUser(@Param("user") User user);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.app.toaster.category.service;

import com.app.toaster.category.controller.request.ChangeCateoryPriorityDto;
import com.app.toaster.category.controller.request.ChangeCategoryPriorityDto;
import com.app.toaster.category.controller.request.ChangeCateoryTitleDto;
import com.app.toaster.category.controller.request.CreateCategoryDto;
import com.app.toaster.category.controller.response.CategoriesResponse;
Expand Down Expand Up @@ -41,7 +41,7 @@ public class CategoryService {
private final CategoryRepository categoryRepository;
private final ToastRepository toastRepository;

private final static int MAX_CATERGORY_NUMBER = 15;
private final static int MAX_CATEGORY_NUMBER = 15;
private final TimerRepository timerRepository;

@Transactional
Expand All @@ -52,9 +52,8 @@ public void createCategory(final Long userId, final CreateCategoryDto createCate
val maxPriority = categoryRepository.findMaxPriorityByUser(presentUser);

val categoryNum = categoryRepository.countAllByUser(presentUser);
System.out.println(categoryNum);

if (categoryNum >= MAX_CATERGORY_NUMBER) {
if (categoryNum >= MAX_CATEGORY_NUMBER) {
throw new CustomException(Error.BAD_REQUEST_CREATE_CLIP_EXCEPTION,
Error.BAD_REQUEST_CREATE_CLIP_EXCEPTION.getMessage());
}
Expand Down Expand Up @@ -141,24 +140,26 @@ public GetCategoryResponseDto getCategory(final Long userId, final Long category

//순서 업데이트
@Transactional
public void editCategoryPriority(ChangeCateoryPriorityDto changeCateoryPriorityDto) {
public void editCategoryPriority(ChangeCategoryPriorityDto changeCategoryPriorityDto) {
val newPriority = changeCategoryPriorityDto.newPriority();

val newPriority = changeCateoryPriorityDto.newPriority();

Category category = categoryRepository.findById(changeCateoryPriorityDto.categoryId())
.orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_CATEGORY_EXCEPTION,
Error.NOT_FOUND_CATEGORY_EXCEPTION.getMessage()));
Category category = categoryRepository.findById(changeCategoryPriorityDto.categoryId())
.orElseThrow(() -> new NotFoundException(Error.NOT_FOUND_CATEGORY_EXCEPTION,
Error.NOT_FOUND_CATEGORY_EXCEPTION.getMessage()));

int currentPriority = category.getPriority();
category.updateCategoryPriority(changeCateoryPriorityDto.newPriority());

if (currentPriority < newPriority)
categoryRepository.decreasePriorityByOne(changeCateoryPriorityDto.categoryId(), currentPriority,
newPriority, category.getUser().getUserId());
else if (currentPriority > newPriority)
categoryRepository.increasePriorityByOne(changeCateoryPriorityDto.categoryId(), currentPriority,
newPriority, category.getUser().getUserId());
if (currentPriority < newPriority) {
categoryRepository.findAllByPriorityBetweenForUpdate(currentPriority, newPriority);
categoryRepository.decreasePriorityByOne(changeCategoryPriorityDto.categoryId(), currentPriority,
newPriority, category.getUser().getUserId());
} else {
categoryRepository.findAllByPriorityBetweenForUpdate(newPriority, currentPriority);
categoryRepository.increasePriorityByOne(changeCategoryPriorityDto.categoryId(), currentPriority,
newPriority, category.getUser().getUserId());
}

category.updateCategoryPriority(changeCategoryPriorityDto.newPriority());
}

@Transactional
Expand Down
1 change: 0 additions & 1 deletion linkmind/src/main/resources/data.sql
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.app.toaster.category.service;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import org.checkerframework.checker.units.qual.A;
import org.checkerframework.checker.units.qual.C;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Profile;
import org.springframework.test.context.ActiveProfiles;

import com.app.toaster.auth.controller.AuthController;
import com.app.toaster.auth.service.AuthService;
import com.app.toaster.auth.service.kakao.KakaoSignInService;
import com.app.toaster.category.controller.request.ChangeCategoryPriorityDto;
import com.app.toaster.category.domain.Category;
import com.app.toaster.category.infrastructure.CategoryRepository;
import com.app.toaster.user.domain.SocialType;
import com.app.toaster.user.domain.User;
import com.app.toaster.user.infrastructure.UserRepository;

import static org.assertj.core.api.Assertions.assertThat;

import com.app.toaster.user.domain.User;
import com.app.toaster.user.domain.SocialType;
import com.app.toaster.user.infrastructure.UserRepository;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

@SpringBootTest
@ActiveProfiles(profiles = "local")
class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Autowired
private CategoryRepository categoryRepository;

@Autowired
private CategoryService categoryService;

@Autowired
private PlatformTransactionManager transactionManager;

private User user;
private Category targetCategory;

@BeforeEach
void setUp() {
user = userRepository.findByUserId(7L).orElseThrow();

Category category1 = new Category("category1", user, 1);
Category category2 = new Category("category2", user, 2);
Category category3 = new Category("category3", user, 3);
Category category4 = new Category("category4", user, 4);
Category category5 = new Category("category5", user, 5);

categoryRepository.saveAll(List.of(category1, category2, category3, category4, category5));
targetCategory = category3;
}

@Test
@DisplayName("우선순위를 증가하여 수정힌다.")
void increaseCategoryPriority() {
// Given

// When
categoryService.editCategoryPriority(new ChangeCategoryPriorityDto(targetCategory.getCategoryId(), 5));

// Then
List<Category> categoryList = categoryRepository.findAllByUserOrderByPriority(user);
assertThat(categoryList.get(0).getTitle()).isEqualTo("category1");
assertThat(categoryList.get(1).getTitle()).isEqualTo("category2");
assertThat(categoryList.get(2).getTitle()).isEqualTo("category4");
assertThat(categoryList.get(3).getTitle()).isEqualTo("category5");
assertThat(categoryList.get(4).getTitle()).isEqualTo("category3");
}

@Test
@DisplayName("우선순위를 감소하여 수정힌다.")
void decreaseCategoryPriority() {
// Given

// When
categoryService.editCategoryPriority(new ChangeCategoryPriorityDto(targetCategory.getCategoryId(), 1));

// Then
List<Category> categoryList = categoryRepository.findAllByUserOrderByPriority(user);
assertThat(categoryList.get(0).getTitle()).isEqualTo("category3");
assertThat(categoryList.get(1).getTitle()).isEqualTo("category1");
assertThat(categoryList.get(2).getTitle()).isEqualTo("category2");
assertThat(categoryList.get(3).getTitle()).isEqualTo("category4");
assertThat(categoryList.get(4).getTitle()).isEqualTo("category5");
}

@Test
@DisplayName("SELECT FOR UPDATE가 동시에 접근할 경우 대기하거나 충돌하는지 확인한다.")
void testSelectForUpdateConcurrency() throws ExecutionException, InterruptedException {
// 트랜잭션 템플릿 설정
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

CompletableFuture<Void> transaction1 = CompletableFuture.runAsync(() -> {
transactionTemplate.execute(status -> {
categoryService.editCategoryPriority(new ChangeCategoryPriorityDto(targetCategory.getCategoryId(), 5));
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
return null;
});
});

CompletableFuture<Void> transaction2 = CompletableFuture.runAsync(() -> {
transactionTemplate.execute(status -> {
categoryService.editCategoryPriority(new ChangeCategoryPriorityDto(targetCategory.getCategoryId(), 1));
return null;
});
});

CompletableFuture.allOf(transaction1, transaction2).join();

// 결과 확인
List<Category> categoryList = categoryRepository.findAllByUserOrderByPriority(user);
assertThat(categoryList.get(0).getTitle()).isEqualTo("category3");
assertThat(categoryList.get(1).getTitle()).isEqualTo("category1");
assertThat(categoryList.get(2).getTitle()).isEqualTo("category2");
assertThat(categoryList.get(3).getTitle()).isEqualTo("category4");
assertThat(categoryList.get(4).getTitle()).isEqualTo("category3");
}

@AfterEach
void finish(){
categoryRepository.deleteAll();
}


}