diff --git a/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java b/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java index 1886234..135452f 100644 --- a/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java +++ b/backend/src/main/java/com/worlabel/domain/image/controller/ImageController.java @@ -56,12 +56,10 @@ public class ImageController { @Operation(summary = "이미지에 대한 Presigned ", description = "이미지에 대한 Presigned 주소를 받아옵니다.") @SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR}) public List uploadFolderByPresignedImage( - @CurrentUser final Integer memberId, @RequestBody final List imageMetaList, @PathVariable("project_id") final Integer projectId, @PathVariable("folder_id") final Integer folderId) { - log.debug("requestImageList {}", imageMetaList); - return imageService.uploadFolderByPresignedImage(memberId, imageMetaList, projectId, folderId); + return imageService.uploadFolderByPresignedImage(imageMetaList, projectId, folderId); } @GetMapping("/folders/{folder_id}/images/{image_id}") @@ -81,7 +79,6 @@ public class ImageController { @Operation(summary = "이미지 폴더 이동", description = "이미지가 위치한 폴더를 변경합니다.") @SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR, ErrorCode.PARTICIPANT_EDITOR_UNAUTHORIZED}) public void moveFolderImage( - @CurrentUser final Integer memberId, @PathVariable("folder_id") final Integer folderId, @PathVariable("project_id") final Integer projectId, @PathVariable("image_id") final Long imageId, @@ -98,7 +95,6 @@ public class ImageController { @PathVariable("folder_id") final Integer folderId, @PathVariable("project_id") final Integer projectId, @PathVariable("image_id") final Long imageId) { - log.debug("project: {} , folder: {}, 삭제하려는 이미지: {}, 현재 로그인 중인 사용자 : {}", projectId, folderId, imageId); imageService.deleteImage(projectId, folderId, imageId); } diff --git a/backend/src/main/java/com/worlabel/domain/image/repository/ImageBulkRepository.java b/backend/src/main/java/com/worlabel/domain/image/repository/ImageBulkRepository.java new file mode 100644 index 0000000..32f6bc7 --- /dev/null +++ b/backend/src/main/java/com/worlabel/domain/image/repository/ImageBulkRepository.java @@ -0,0 +1,41 @@ +package com.worlabel.domain.image.repository; + +import com.worlabel.domain.image.entity.Image; +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.sql.PreparedStatement; +import java.sql.Timestamp; +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class ImageBulkRepository { + + private final JdbcTemplate jdbcTemplate; + + @Transactional + public void saveAll(List imageList) { + String sql = "INSERT INTO project_image (image_extension,folder_id,image_key,status,image_title)" + + " values (?, ?, ?, ?, ?)"; + + int batchSize = 1000; // 배치 크기 설정 + for (int i = 0; i < imageList.size(); i += batchSize) { + List batchList = imageList.subList(i, Math.min(i + batchSize, imageList.size())); + + jdbcTemplate.batchUpdate(sql, + batchList, + batchList.size(), + (PreparedStatement ps, Image image) -> { + ps.setString(1, image.getExtension()); // image_extension (String) + ps.setInt(2, image.getFolder().getId()); // folder_id (Integer) + ps.setString(3, image.getImageKey()); // image_key (String) + ps.setString(4, image.getStatus().name()); // status (String or Enum) + ps.setString(5, image.getTitle()); // image_title (String) + }); + } + } + +} 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 e6e07f6..e9576ab 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 @@ -2,6 +2,7 @@ package com.worlabel.domain.image.service; import com.worlabel.domain.folder.entity.Folder; import com.worlabel.domain.image.entity.Image; +import com.worlabel.domain.image.repository.ImageBulkRepository; import com.worlabel.domain.image.repository.ImageRepository; import com.worlabel.global.exception.CustomException; import com.worlabel.global.exception.ErrorCode; @@ -25,6 +26,7 @@ public class ImageAsyncService { private final S3UploadService s3UploadService; private final ImageRepository imageRepository; + private final ImageBulkRepository imageBulkRepository; private final ThreadPoolTaskExecutor imageUploadExecutor; @Async("imageUploadExecutor") @@ -72,4 +74,10 @@ public class ImageAsyncService { private String getExtension(String fileName) { return fileName.substring(fileName.lastIndexOf(".") + 1); } + + @Transactional + @Async("imageUploadExecutor") + public void saveImages(final List imageList) { + imageBulkRepository.saveAll(imageList); + } } 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 9276c3f..a01e609 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 @@ -7,6 +7,7 @@ import com.worlabel.domain.folder.repository.FolderRepository; import com.worlabel.domain.image.entity.Image; import com.worlabel.domain.image.entity.LabelStatus; import com.worlabel.domain.image.entity.dto.*; +import com.worlabel.domain.image.repository.ImageBulkRepository; import com.worlabel.domain.image.repository.ImageRepository; import com.worlabel.domain.participant.entity.PrivilegeType; import com.worlabel.domain.project.entity.Project; @@ -22,6 +23,7 @@ import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.compress.utils.IOUtils; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -321,13 +323,12 @@ public class ImageService { @Transactional @CheckPrivilege(PrivilegeType.EDITOR) - public List uploadFolderByPresignedImage(final Integer memberId, - final List imageMetaList, + public List uploadFolderByPresignedImage(final List imageMetaList, final Integer projectId, final Integer folderId) { Folder folder = getOrCreateFolder(folderId, projectId); List presignedUrls = new ArrayList<>(); - + List imageList = new ArrayList<>(); for (ImageMetaRequest meta : imageMetaList) { // UUID 생성 및 이미지 Key 지정 String key = UUID.randomUUID().toString().substring(0,13); @@ -336,21 +337,22 @@ public class ImageService { // Presigned URL 생성 String presignedUrl = s3UploadService.generatePresignedUrl(key, extension); - log.debug("presignedUrl {}", presignedUrl); // DB에 이미지 메타데이터 저장 Image image = Image.of(fileName, s3UploadService.addBucketPrefix(key), extension, folder); - imageRepository.save(image); + imageList.add(image); // Presigned URL과 함께 응답 데이터 생성 ImagePresignedUrlResponse response = ImagePresignedUrlResponse.of(meta.getId(), presignedUrl); presignedUrls.add(response); } + imageAsyncService.saveImages(imageList); + log.debug("이미지 개수 {}",presignedUrls.size()); return presignedUrls; } - // 이미지 가져오면서 프로젝트 소속 여부를 확인 + // 이미지 가져오면서 프로젝트 소속 여부를 확인 private Image getImageByIdAndFolderIdAndFolderProjectId(final Integer folderId, final Long imageId, final Integer projectId) { return imageRepository.findByIdAndFolderIdAndFolderProjectId(imageId, folderId, projectId) .orElseThrow(() -> new CustomException(ErrorCode.DATA_NOT_FOUND));