UncheckedIOException 해결

HD.Y·2024년 1월 4일

에러 정리

목록 보기
1/1
post-thumbnail

실습 진행 간 발생한 오류 정리

  • 오늘부터 내일까지는 강사님이 제공해주시는 Swagger UI 문서에 따라 기능들을 구현하는 시간을 갖는다. 실습 진행 간 발생한 에러를 기록해두기 위해 이 글을 적는다.

  • 에러는 상품을 생성할 때 발생했다. 상품을 생성할 때 이지지 파일을 여러개 등록하여 생성이 가능토록 구현하는 거였는데, 로컬 저장소에 구현했을때는 정상적으로 작동되었다.

  • 하지만, 상품 이미지를 불러오기 위해 수업때 배운 AWS 클라우드를 활용하여, 클라우드 저장소에 이미지를 업로드 하고 클라우드 저장소에서 이미지를 불러오도록 구현하면 되는 거였다.

  • 이미 지난 수업시간때 코드를 다 짜고, 작동 테스트까지 해봤기 때문에 손쉽게 구현할 줄 알았지만, 여기서 UncheckedIOException 에러가 내 발목을 잡았다. 분명 예전에는 정상적으로 동작한 코드를 그대로 사용하였는데 왜 에러가 발생하는지 이유를 모르겠었다.

  • 먼저 에러가 발생한 코드는 상품서비스 클래스에서 AWS 클라우드 저장소에 이미지를 업로드 하는 부분에서 발생하였다. 코드는 아래와 같다.

💻 Product 엔티티

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer idx;

    @OneToMany(mappedBy = "product")
    private List<ProductImage> productImageList = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_idx")
    private Member member;

    private String name;
    private Integer categoryIdx;
    private Integer price;
    private Integer salePrice;
    private String deliveryType;
    private String isTodayDeal;
    private String contents;
}

💻 ProductImage 엔티티

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProductImage {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer idx;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_idx")
    private Product product;

    private String image;
}

💻 ProductController 클래스

@RestController
@RequiredArgsConstructor
@RequestMapping("/product")
@CrossOrigin("*")
public class ProductController {

    private final ProductService productService;

    @RequestMapping(method = RequestMethod.POST, value = "/create")
    public ResponseEntity createProduct(@RequestPart PostProductReq postProductReq, @RequestPart MultipartFile[] uploadFiles) throws IOException {
        ProductBaseRes productBaseRes = productService.createProduct(postProductReq, uploadFiles);

        return ResponseEntity.ok().body(productBaseRes);
    }
}

💻 ProductService 클래스

@Service
public class ProductService {

    @Value("${project.upload.path}")
    private String uploadPath;

    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    private AmazonS3 s3;
    private final ProductRepository productRepository;
    private final ProductImageRepository productImageRepository;

    public ProductService(AmazonS3 s3, ProductRepository productRepository, ProductImageRepository productImageRepository) {
        this.s3 = s3;
        this.productRepository = productRepository;
        this.productImageRepository = productImageRepository;
    }

    public String makeFolder(){
        String str = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
        String folderPath = str.replace("/", File.separator);

        File uploadPathFolder = new File(uploadPath, folderPath);

        if(uploadPathFolder.exists() == false) {
            uploadPathFolder.mkdirs();
        }
        return folderPath;
    }

    public String saveFile(MultipartFile file) {

        String originalName = file.getOriginalFilename();

        String folderPath = makeFolder();
        String uuid = UUID.randomUUID().toString();
        String saveFileName = folderPath+ File.separator + uuid + "_" + originalName;


        try {

            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(file.getSize());
            metadata.setContentType(file.getContentType());

            // 🔥에러가 발생한 부분🔥
            s3.putObject(bucket, saveFileName.replace(File.separator, "/"), file.getInputStream(), metadata);

        } catch (IOException e) {
            throw new RuntimeException(e);
        } 
        return s3.getUrl(bucket, saveFileName.replace(File.separator, "/")).toString();
    }

    public ProductBaseRes createProduct(PostProductReq postProductReq, MultipartFile[] uploadFiles) {

        Member member = (Member) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        Product product = Product.builder()
                .member(member)
                .name(postProductReq.getName())
                .categoryIdx(postProductReq.getCategoryIdx())
                .price(postProductReq.getPrice())
                .salePrice(postProductReq.getSalePrice())
                .deliveryType(postProductReq.getDeliveryType())
                .isTodayDeal(postProductReq.getIsTodayDeal())
                .contents(postProductReq.getContents())
                .build();

        // 상품 테이블에 저장
        Product result = productRepository.save(product);

        // 상품 이미지 테이블에 저장
        for(MultipartFile multipartFile : uploadFiles) {

            String saveFileName = saveFile(multipartFile);

            productImageRepository.save(ProductImage.builder()
                            .product(result)
                            .image(saveFileName.replace(File.separator, "/"))
                    .build());
        }

        ProductBaseRes productBaseRes = ProductBaseRes.builder()
                .isSuccess(true)
                .code(1000)
                .message("요청 성공")
                .result(PostProductRes.builder()
                        .idx(result.getIdx())
                        .build())
                .success(true)
                .build();

        return productBaseRes;

    }

  • 디버깅을 했을때, ProductService 클래스에서 표시한 부분 다음으로 넘어갈때 아래와 같은 에러가 등장했다.
    java.io.UncheckedIOException: Cannot delete C:\Users\????\AppData\Local\Temp\tomcat.8080.8028046931035505689\work\Tomcat\localhost\ROOT\upload_9862159e_c81a_4c5d_a095_fdf656acb30a_00000011.tmp

  • 구글링을 해보고, 챗 gpt에도 물어보고 하는데 정확하게 나와있는 정보가 없었다. 에러를 읽어보면 임시파일을 삭제할 수가 없다고 나오는데 임시파일은 무엇이고, 임시파일을 왜 삭제하지❓ 라는 의문만 들었다.

  • 분명 나는 삭제하라는 코드를 작성한 적이 없는데 도통 원인을 몰랐었다.

  • 그러다가 간신히 알아낸 방법이 있었다. 우리가 클라우드 저장소에 이미지를 업로드 하게되면 사실 로컬 저장소에는 더이상 이미지를 저장할 필요가 없다.

    따라서 이것을 강제로 삭제할 수 있도록 아래와 같이 ProductService 클래스를 수정해줬다. 수정하고 나서 테스트해보니 정상적으로 클라우드 저장소에 이미지가 업로드 되는 것을 확인 할 수 있었다.

    수정 부분

            try {
    
               ObjectMetadata metadata = new ObjectMetadata();
               metadata.setContentLength(file.getSize());
               metadata.setContentType(file.getContentType());
    
               s3.putObject(bucket, saveFileName.replace(File.separator, "/"), file.getInputStream(), metadata);
    
           } catch (IOException e) {
               throw new RuntimeException(e);
               
               // 🔥 여기 부터 수정부분 🔥
           }  finally {
               // 로컬 저장소에 파일이 남아 있으면 삭제
               File localFile = new File(saveFileName);
               if (localFile.exists()) {
                   localFile.delete();
               }
           }
           
           
           // 🔥 또 다른 방법 : system.gc() 를 try 부분에 넣어주기 ( 가비지 컬렉션 삭제 )
profile
Backend Developer

0개의 댓글