diff --git a/backend/src/main/java/com/worlabel/domain/image/service/ImageAsyncService.java b/backend/src/main/java/com/worlabel/domain/image/service/ImageAsyncService.java index b4ca8d5..e6e07f6 100644 --- a/backend/src/main/java/com/worlabel/domain/image/service/ImageAsyncService.java +++ b/backend/src/main/java/com/worlabel/domain/image/service/ImageAsyncService.java @@ -11,7 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -30,19 +29,18 @@ public class ImageAsyncService { @Async("imageUploadExecutor") @Transactional(propagation = Propagation.REQUIRES_NEW) - public CompletableFuture asyncImageUpload(final List imageFileList, final Folder folder, final Integer projectId) { + public CompletableFuture asyncImageUpload(final List imageFileList, final Folder folder, final Integer projectId) { log.debug("현재 스레드 - {} 업로드 파일 개수 - {}, 현재 작업 큐 용량 - {}", Thread.currentThread().getName(), imageFileList.size(), - imageUploadExecutor.getThreadPoolExecutor().getQueue().size()); // 큐에 쌓인 작업 수 출력); + imageUploadExecutor.getThreadPoolExecutor().getQueue().size() + ); // 큐에 쌓인 작업 수 출력); imageFileList.forEach(file -> { - try{ - String extension = getExtension(file.getOriginalFilename()); - String imageKey = s3UploadService.uploadImageFile(file, extension, projectId); - + try { + String imageKey = imageUpload(projectId, file); createImage(file, imageKey, folder); - }catch (Exception e){ + } catch (Exception e) { log.error("이미지 업로드 실패: {}", file.getOriginalFilename(), e); throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE); } @@ -52,15 +50,19 @@ public class ImageAsyncService { return CompletableFuture.completedFuture(null); } - @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED) + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public String imageUpload(Integer projectId, MultipartFile file) { + String extension = getExtension(file.getOriginalFilename()); + return s3UploadService.uploadImageFile(file, extension, projectId); + } + public void createImage(MultipartFile file, String imageKey, Folder folder) { try { String name = file.getOriginalFilename(); String extension = getExtension(name); - log.debug("image 파일 {} {} {} {}", name, extension, imageKey, folder.getId()); Image image = Image.of(name, imageKey, extension, folder); imageRepository.save(image); - }catch (Exception e){ + } catch (Exception e) { log.debug("이미지 DB 저장 실패: ", e); s3UploadService.deleteImageFromS3(imageKey); throw new CustomException(ErrorCode.FAIL_TO_CREATE_FILE); diff --git a/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java b/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java index a98c51e..d055599 100644 --- a/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java +++ b/backend/src/main/java/com/worlabel/domain/image/service/ImageService.java @@ -40,7 +40,6 @@ import java.util.stream.Stream; @Slf4j @Service -@Transactional @RequiredArgsConstructor public class ImageService { @@ -56,9 +55,8 @@ public class ImageService { @CheckPrivilege(value = PrivilegeType.EDITOR) public void uploadImageList(final List imageList, final Integer folderId, final Integer projectId) { Folder folder = getOrCreateFolder(folderId, projectId); - folderRepository.flush(); - log.debug("folder Id {}, Project Id {}",folder.getId(), folder.getProject().getId()); + log.debug("folder Id {}, Project Id {}", folder.getId(), folder.getProject().getId()); long prev = System.currentTimeMillis(); // 동적 배치 크기 계산 @@ -78,7 +76,6 @@ public class ImageService { List batch = imageList.subList(i, Math.min(i + batchSize, imageList.size())); CompletableFuture future = imageAsyncService.asyncImageUpload(batch, folder, projectId); - // 모든 비동기 작업이 완료될 때까지 기다림 futures.add(future); } @@ -308,7 +305,8 @@ public class ImageService { String currentDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Project project = getProject(projectId); Folder folder = Folder.of(currentDateTime, null, project); - folderRepository.save(folder); // 새로운 폴더를 저장 + folderRepository.saveAndFlush(folder); // 새로운 폴더를 저장 + return folder; } } diff --git a/backend/src/main/java/com/worlabel/domain/project/entity/Project.java b/backend/src/main/java/com/worlabel/domain/project/entity/Project.java index 1d8ab5c..e2bccf5 100644 --- a/backend/src/main/java/com/worlabel/domain/project/entity/Project.java +++ b/backend/src/main/java/com/worlabel/domain/project/entity/Project.java @@ -1,6 +1,7 @@ package com.worlabel.domain.project.entity; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.worlabel.domain.folder.entity.Folder; import com.worlabel.domain.labelcategory.entity.ProjectCategory; import com.worlabel.domain.model.entity.AiModel; import com.worlabel.domain.workspace.entity.Workspace; @@ -54,6 +55,12 @@ public class Project extends BaseEntity { @OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List categoryList = new ArrayList<>(); + /** + * 프로젝트에 속한 폴더 + */ + @OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + private List folderList = new ArrayList<>(); + /** * 프로젝트에 속한 모델 */ @@ -74,4 +81,8 @@ public class Project extends BaseEntity { this.title = title; this.projectType = projectType; } + + public void createFolder(final Folder folder) { + folderList.add(folder); + } }