8w8u8

이미지 업로드 기능 작업 중 생긴 여러 문제들.. 본문

back-end

이미지 업로드 기능 작업 중 생긴 여러 문제들..

jud1th 2025. 7. 5. 19:02

Image is abstract; cannot be instantiated

추상클래스는 인스턴스화가 불가하다!

@Getter
// @Builder 이게 문제였음!
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@DiscriminatorColumn
@Inheritance(strategy = InheritanceType.JOINED)
@Entity
public abstract class Image {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    protected String originalName;
    protected String storedName;
    protected String imgPath;

    private final static String[] extensionArr={"jpg","jpeg","bmp","gif","png"};
    public void setImgPath(String imgPath) {
        this.imgPath = imgPath;
    }

    // 이미지 파일의 확장자를 추출하는 메서드
    public String extractExtension(String originalName) {
        int index = originalName.lastIndexOf('.');
        return originalName.substring(index + 1);
    }

    // 저장할 파일 이름을 생성하는 메서드
    public String generateStoreName(String originalName) {
        String extension = extractExtension(originalName);
        if (!checkValidation(extension))
            throw new RuntimeException(extension + " 은 지원하지 않는 확장자입니다.");
        return UUID.randomUUID() + "." + extension;
    }

    public boolean checkValidation(String extension) {
        return Arrays.stream(extensionArr).anyMatch(value -> value.equals(extension));
    }

reviewImg의 review_id가 null로 저장되던 문제

save()의 잘못된 위치가 원인

Review 객체가 데이터베이스에 먼저 저장되어야 review의 id가 할당되고, 이후에 ReviewImg 객체들이 해당 review의 id를 참조할 수 있다.

    public Review registerReview(Long productId, ReviewRequestDto dto, List<MultipartFile> files) throws IOException {
        Product product = productService.findProductById(productId);
        Member member = memberService.findMemberById(dto.getMemberId());

        Review review = dto.toEntity(product, member);
        reviewRepository.save(review); // 해결

        for (MultipartFile multipartFile : files) {
            if (multipartFile != null && !multipartFile.isEmpty()) {
                String originalName = multipartFile.getOriginalFilename();
                String storedName = UUID.randomUUID() + "-" + originalName;
                String imgPath = imgService.saveImage(multipartFile, storedName);

                ReviewImg reviewImg = ReviewImg.builder()
                        .originalName(originalName)
                        .storedName(storedName)
                        .imgPath(imgPath)
                        .review(review)
                        .build();

                review.addReviewImage(reviewImg);
                reviewImgRepository.save(reviewImg);
            }
        }

 //       reviewRepository.save(review); 원인
        return review;
    }

해결


CascadeType.REMOVE vs @OnDelete(action = OnDeleteAction.CASCADE) : 무엇을 사용해야할까?

둘 다 부모 엔티티가 삭제될 때 연관된 자식 엔티티도 삭제하는 기능을 수행하는데,

  • CascadeType.REMOVE는 JPA 수준에서
  • @OnDelete(action = OnDeleteAction.CASCADE) 는 데이터베이스 수준에서 처리된다.

CascadeType.REMOVE

  • CascadeType.REMOVEJPA 수준에서 부모 엔티티가 삭제될 때 연관된 자식 엔티티도 함께 삭제되도록 설정한다.
  • 장점
    • 데이터베이스 독립성 : 다양한 데이터베이스에서 동일하게 동작하고, 데이터베이스 변경 시 문제 발생이 적다.
    • JPA에서 프로그램적으로 의존성을 관리하므로 운영자가 실수로 의존성을 가진 레코드를 삭제하는 일을 줄일 수 있다.
  • 단점
    • 자식 엔티티 개수만큼 DELETE 쿼리가 생성되므로 대규모 데이터 삭제 시 성능 부담이 크다.
    • @OneToMany 관계에 어노테이션을 추가해야 하므로 양방향 의존 관계를 설정해야 할 수 있다.
  • 애플리케이션 로직에서 엔티티 상태를 세밀하게 관리해야 하는 경우 유리하다.
  • e.g. 블로그 포스트와 댓글처럼 애플리케이션 내부에서 여러 상태 변화가 자주 발생하는 경우.

@OnDelete(action = OnDeleteAction.CASCADE)

  • @OnDelete(action = OnDeleteAction.CASCADE)데이터베이스 수준에서 부모 엔티티가 삭제될 때 연관된 자식 엔티티도 함께 삭제되도록 설정한다.
  • 장점
    • 단일 DELETE 쿼리로 연쇄 삭제를 처리하여 성능이 뛰어나다.
  • 단점
    • 특정 데이터베이스에 의존적이며, 데이터베이스 변경 시 설정을 다시 해야할 수도 있다.
  • 데이터베이스 성능이 중요하고 대규모 데이터 삭제 작업이 필요한 경우 유리하다.
  • e.g. 주문과 주문 항목처럼 데이터베이스 수준에서 무결성을 유지하면서 대량의 데이터를 효율적으로 삭제해야 하는 경우.

그래서 나는...

리뷰와 리뷰이미지 사이의 관계에서, 나는 @OnDelete(action = OnDeleteAction.CASCADE) 를 사용했다.

@Entity
public class ReviewImg extends Image {
    // FK
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "review_id", updatable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Review review;