✳️ JPA @SQLRestriction, @ElementCollection 사용법

devdo·2024년 4월 7일

JPA

목록 보기
6/18
post-thumbnail

소프트 삭제

여러 연관관계가 많은 엔티티에는 삭제시, 많은 오류를 가져올 수 있습니다. 그래서 실무에서는 deleted 필드를 두고 업데이트해서 실제 삭제가 아닌, 소프트웨어적으로 삭제하는 방식을 많이 사용합니다.

JPA 에서는 간단하게 처리하는 어노테이션이 있는데, 바로

@SQLRestriction 입니다.

이 어노테이션을 사용하면, 간단하게 조회에서 자동 제외하게 시킬 수 있습니다!

코드 예시

@DynamicUpdate
@Builder
@Entity
@Table(name = "posts")
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SQLRestriction("deleted = false") // 소프트 삭제된 게시글은 조회에서 자동 제외
public class Post extends BaseTimeEntity {

...

    @Column(nullable = false)
    @org.hibernate.annotations.Comment("게시글 삭제 여부 (소프트 삭제)")
    @Builder.Default
    private boolean deleted = false;

Service 로직

    @Override
    @Transactional
    public void deletePost(Long postId, String authorEmail) {
        log.info("게시글 삭제 요청: ID={}, 작성자={}", postId, authorEmail);
        
        Post post = findPostById(postId);
        
        // 작성자 권한 확인
        if (!post.isAuthor(authorEmail)) {
            throw new CustomException(ErrorCode.UNAUTHORIZED_POST_MODIFICATION);
        }
        
        // ✅ 소프트 삭제 (deleted 플래그 변경, @SQLRestriction에 의해 조회 시 자동 필터링)
        post.softDelete();
        log.info("게시글 소프트 삭제 완료: ID={}", postId);
    }

@ElementCollection 사용법

Product 엔티티


	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long pno;


    @ElementCollection
    private List<ProductImage> imageList = new ArrayList<>();

ProductImage

@Embeddable // 임베디드 할 수 있다는 걸 체크!
@Getter
@ToString
@NoArgsConstructor
public class ProductImage {

    private String fileName;

    @Setter
    private int ord;

    @Builder
    public ProductImage(String fileName, int ord) {
        this.fileName = fileName;
        this.ord = ord;
    }
}

실제 DB 상의 필드

product_image 테이블

특징

  • 값 타입 컬렉션은 자신의 라이프 사이클을 가지지 않는다.
    이것은 마치 영속성 전이(Cascade)와 고아 객체 제거 기능을 필수로 가진 것과 비슷하다고 볼 수 있다.

  • 또한 기본적으로 @ElementCollection이 LAZY로 설정돼 있어 지연로딩으로 작동한다.


만약 값 타입 컬렉션의 값을 수정하기 위해서는 어떻게 해야 할까?

값 타입 컬렉션 안의 데이터를 수정할 때는 일부만 수정하는 것이 아닌 데이터를 삭제 후 변경된 데이터 전체를 새로 추가해줘야 한다.

부모엔티티인 Product 내 그 로직이 담겨져 있다.

Product 엔티티 내 imageList 추가/수정 메서드

    public void addImage(ProductImage image) {
        if(this.imageList == null) {
            this.imageList = new ArrayList<>();
        }
        int size = imageList.size();
        image.setOrd(size);
        this.imageList.add(image);
    }

    public void addImageString(String fileName) {
        ProductImage image = ProductImage.builder()
                .fileName(fileName)
                .build();
        addImage(image);
    }
    
        public void clearList() {
        this.imageList.clear();
    }

서비스로직에서 사용시

    @Override
    public Long modify(ProductRequest request) {
        // 조회
        Product product = productRepository.findById(request.getPno())
                .orElseThrow(() -> new RuntimeException("해당 상품이 없습니다. pno: " + request.getPno()));

        // 변경 내용 반영
        product.changeName(request.getPname());
        product.changePrice(request.getPrice());
        product.changeDesc(request.getPdesc());
        product.changeDelFlag(request.isDelFlag());

        // 이미지 처리
        List<String> uploadedFileName = request.getUploadedFileName();
        // 기존 이미지 삭제
        product.clearList();

        if (!CollectionUtils.isEmpty(uploadedFileName)) {
            uploadedFileName.forEach(product::addImageString);
        }

        // 저장
        Product savedProduct = productRepository.save(product);

        return savedProduct.getPno();
    }
profile
자바 스프링 백엔드 개발자입니다. 배운 것을 기록합니다.

0개의 댓글