diff --git a/src/main/java/zipdabang/server/async/AsyncConfig.java b/src/main/java/zipdabang/server/async/AsyncConfig.java new file mode 100644 index 0000000..094d77e --- /dev/null +++ b/src/main/java/zipdabang/server/async/AsyncConfig.java @@ -0,0 +1,41 @@ +package zipdabang.server.async; + +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +@EnableAsync +public class AsyncConfig implements AsyncConfigurer { + + private int CORE_POOL_SIZE = 3; + private int MAX_POOL_SIZE = 8; + private int QUEUE_CAPACITY = 1000; + + @Bean(name = "recipeUploadExecutor") + public Executor threadPoolTaskExecutor() { + ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + + taskExecutor.setCorePoolSize(CORE_POOL_SIZE); + taskExecutor.setMaxPoolSize(MAX_POOL_SIZE); + taskExecutor.setQueueCapacity(QUEUE_CAPACITY); + taskExecutor.setThreadNamePrefix("Executor-"); + + taskExecutor.setTaskDecorator(new CustomDecorator()); + taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + + return taskExecutor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler(); + } + +} diff --git a/src/main/java/zipdabang/server/async/AsyncExceptionHandler.java b/src/main/java/zipdabang/server/async/AsyncExceptionHandler.java new file mode 100644 index 0000000..58d059c --- /dev/null +++ b/src/main/java/zipdabang/server/async/AsyncExceptionHandler.java @@ -0,0 +1,15 @@ +package zipdabang.server.async; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; + +import java.lang.reflect.Method; + +@Slf4j +public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { + + @Override + public void handleUncaughtException(Throwable ex, Method method, Object... params) { + log.error(ex.getMessage(), ex); + } +} diff --git a/src/main/java/zipdabang/server/async/CustomDecorator.java b/src/main/java/zipdabang/server/async/CustomDecorator.java new file mode 100644 index 0000000..ff6170a --- /dev/null +++ b/src/main/java/zipdabang/server/async/CustomDecorator.java @@ -0,0 +1,22 @@ +package zipdabang.server.async; + +import org.springframework.core.task.TaskDecorator; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +public class CustomDecorator implements TaskDecorator { + @Override + public Runnable decorate(Runnable runnable) { + RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); + + return () ->{ + try{ + RequestContextHolder.setRequestAttributes(attributes); + + runnable.run(); + } finally { + RequestContextHolder.resetRequestAttributes(); + } + }; + } +} diff --git a/src/main/java/zipdabang/server/converter/RecipeConverter.java b/src/main/java/zipdabang/server/converter/RecipeConverter.java index 9d26c40..95cebfe 100644 --- a/src/main/java/zipdabang/server/converter/RecipeConverter.java +++ b/src/main/java/zipdabang/server/converter/RecipeConverter.java @@ -25,6 +25,7 @@ import javax.annotation.PostConstruct; import java.io.IOException; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; @@ -746,10 +747,10 @@ public static TestRecipe toTestRecipe(RecipeRequestDto.CreateRecipeDto request, return recipe; } - public static List toTestRecipeCategory(List categoryIds, TestRecipe recipe) { - return categoryIds.stream().parallel() + public static CompletableFuture> toTestRecipeCategory(List categoryIds, TestRecipe recipe) { + return CompletableFuture.completedFuture(categoryIds.stream() .map(recipeCategoryId -> toTestRecipeCategoryMappingDto(recipeCategoryId, recipe)) - .collect(Collectors.toList()); + .collect(Collectors.toList())); } private static TestRecipeCategoryMapping toTestRecipeCategoryMappingDto(Long categoryId, TestRecipe recipe) { @@ -760,8 +761,8 @@ private static TestRecipeCategoryMapping toTestRecipeCategoryMappingDto(Long cat .build(); } - public static List toTestStep(RecipeRequestDto.CreateRecipeDto request, TestRecipe recipe, List stepImages) { - return request.getSteps().stream().parallel() + public static CompletableFuture> toTestStep(RecipeRequestDto.CreateRecipeDto request, TestRecipe recipe, List stepImages) { + return CompletableFuture.completedFuture(request.getSteps().stream() .map(step-> { if (step.getDescription() == null) throw new RecipeException(CommonStatus.NULL_RECIPE_ERROR); @@ -771,7 +772,8 @@ public static List toTestStep(RecipeRequestDto.CreateRecipeDto request throw new RuntimeException(e); } }) - .collect(Collectors.toList()); + .collect(Collectors.toList()) + ); } private static TestStep toTestStepDto(RecipeRequestDto.StepDto step, TestRecipe recipe, List stepImages) throws IOException { @@ -803,10 +805,10 @@ private static TestStep toTestStepDto(RecipeRequestDto.StepDto step, TestRecipe return createdStep; } - public static List toTestIngredient(RecipeRequestDto.CreateRecipeDto request, TestRecipe recipe) { - return request.getIngredients().stream().parallel() + public static CompletableFuture> toTestIngredient(RecipeRequestDto.CreateRecipeDto request, TestRecipe recipe) { + return CompletableFuture.completedFuture(request.getIngredients().stream() .map(ingredient -> toTestIngredientDto(ingredient, recipe)) - .collect(Collectors.toList()); + .collect(Collectors.toList())); } private static TestIngredient toTestIngredientDto(RecipeRequestDto.NewIngredientDto ingredient, TestRecipe recipe) { diff --git a/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java b/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java index bf737aa..8a21dec 100644 --- a/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java +++ b/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java @@ -21,6 +21,7 @@ import zipdabang.server.domain.inform.PushAlarm; import zipdabang.server.domain.member.*; import zipdabang.server.domain.recipe.*; +import zipdabang.server.domain.test.QTestRecipe; import zipdabang.server.domain.test.TestRecipe; import zipdabang.server.firebase.fcm.service.FirebaseService; import zipdabang.server.repository.AlarmRepository.AlarmCategoryRepository; @@ -45,6 +46,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Slf4j @@ -905,29 +907,40 @@ public RecipeCategory getRecipeCategory(Long categoryId) { @Override @Transactional(readOnly = false) public TestRecipe testCreate(RecipeRequestDto.CreateRecipeDto request, MultipartFile thumbnail, List stepImages) throws IOException { - TestRecipe buildRecipe = RecipeConverter.toTestRecipe(request, thumbnail); - TestRecipe recipe = testRecipeRepository.save(buildRecipe); - RecipeConverter.toTestRecipeCategory(request.getCategoryId(),recipe).stream() - .map(categoryMapping -> testRecipeCategoryMappingRepository.save(categoryMapping)) - .collect(Collectors.toList()) - .stream() - .map(categoryMapping -> categoryMapping.setRecipe(recipe)); - - - RecipeConverter.toTestStep(request, recipe, stepImages).stream() - .map(step -> testStepRepository.save(step)) - .collect(Collectors.toList()) - .stream() - .map(step -> step.setRecipe(recipe)); - - RecipeConverter.toTestIngredient(request, recipe).stream() - .map(ingredient -> testIngredientRepository.save(ingredient)) - .collect(Collectors.toList()) - .stream() - .map(ingredient -> ingredient.setRecipe(recipe)); - - return recipe; + CompletableFuture savedRecipeFuture = CompletableFuture.supplyAsync(() ->{ + TestRecipe buildRecipe = null; + try { + buildRecipe = RecipeConverter.toTestRecipe(request, thumbnail); + } catch (IOException e) { + throw new RuntimeException(e); + } + return testRecipeRepository.save(buildRecipe); + }); + + savedRecipeFuture.thenAccept(recipe -> { + RecipeConverter.toTestRecipeCategory(request.getCategoryId(),recipe).join().stream() + .map(categoryMapping -> testRecipeCategoryMappingRepository.save(categoryMapping)) + .peek(categoryMapping -> categoryMapping.setRecipe(recipe)); +// .collect(Collectors.toList()) +// .stream() +// .map(categoryMapping -> categoryMapping.setRecipe(recipe)); + }); + + + savedRecipeFuture.thenAccept(recipe -> { + RecipeConverter.toTestStep(request, recipe, stepImages).join().stream() + .map(step -> testStepRepository.save(step)) + .peek(step -> step.setRecipe(recipe)); + }); + + savedRecipeFuture.thenAccept(recipe -> { + RecipeConverter.toTestIngredient(request, recipe).join().stream() + .map(ingredient -> testIngredientRepository.save(ingredient)) + .peek(ingredient -> ingredient.setRecipe(recipe)); + }); + + return savedRecipeFuture.join(); } @Override