Tech Stack/Database

[JPA] [MySQL] ON DELETE CASCADE์™€ ์†Œํ”„ํŠธ ์‚ญ์ œ(Soft Delete)

_silver 2025. 9. 10. 01:58
1. ๋ฌธ์ œ ์ƒํ™ฉ

๊ฒŒ์‹œ๊ธ€(Post)์„ ์‚ญ์ œํ•  ๋•Œ, ๊ทธ ์•ˆ์— ์žˆ๋Š” ๋Œ“๊ธ€(Comment)๋„ ํ•จ๊ป˜ ์‚ญ์ œ ๋˜๋„๋ก ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค.

๊ฒ€์ƒ‰์„ ํ•˜๋‹ค๊ฐ€ @OnDelete ์–ด๋…ธํ…Œ์ด์…˜์„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ , ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€๋ชจ(Post)๋ฅผ ์‚ญ์ œํ•  ๋•Œ ์ž์‹(Comment)๋„ ๊ฐ™์ด ์‚ญ์ œ ๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

 

๊ทธ๋ž˜์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ์šฉํ–ˆ์ง€๋งŒ ๋ฌธ์ œ๋Š” ๊ทธ๋Œ€๋กœ ์˜€๋‹ค.

constraint FKh4c7lvsc298whoyd4w9ta25cr
    foreign key (post_id) references posts (id)

 

์ด ์™ธ๋ž˜ํ‚ค์—๋Š” ON DELETE CASCADE ์˜ต์…˜์ด ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ถ€๋ชจ(Post)๋ฅผ ์‚ญ์ œํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ž์‹(Comment)์ด ๋‚จ์•„์„œ ์•„๋ž˜์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

Cannot delete or update a parent row: a foreign key constraint fails
(`recifit_app`.`comments`, CONSTRAINT `FKh4c7lvsc298whoyd4w9ta25cr`
 FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`))

2. ์‹œ๋„ํ–ˆ๋˜ ๋ฐฉ๋ฒ•: @OnDelete์™€ DB CASCADE

์šฐ์„  ์—”ํ‹ฐํ‹ฐ์— @OnDelete๋ฅผ ์ถ”๊ฐ€ ํ•ด๋ดค๋‹ค.

@ManyToOne(fetch = FetchType.LAZY)
@OnDelete(action = OnDeleteAction.CASCADE) โœ…
private Post post;

 

๊ทธ๋ฆฌ๊ณ  MySQL Workbench์—์„œ ์™ธ๋ž˜ํ‚ค๋ฅผ ON DELETE CASCADE๋กœ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค.

-- ๊ธฐ์กด FK ์‚ญ์ œ -- 
ALTER TABLE comments DROP FOREIGN KEY FKh4c7lvsc298whoyd4w9ta25cr;

-- ์ƒˆ๋กœ ์ƒ์„ฑ: ON DELETE CASCADE ์ถ”๊ฐ€ -- 
ALTER TABLE comments
  ADD CONSTRAINT FKh4c7lvsc298whoyd4w9ta25cr
  FOREIGN KEY (post_id) REFERENCES posts(id)
  ON DELETE CASCADE;

 

์ด์ œ ๋ถ€๋ชจ(Post)๋ฅผ ์ง€์šฐ๋ฉด ์ž์‹(Comment)๋„ ์ž๋™์œผ๋กœ ์‚ญ์ œ๋˜๋Š” ๊ตฌ์กฐ๊ฐ€ ๋˜์—ˆ๋‹ค. 


3. ๋ฌธ์ œ์ : ์†Œํ”„ํŠธ ์‚ญ์ œ์™€ ์ถฉ๋Œ

ํ•˜์ง€๋งŒ Recifit ํ”„๋กœ์ ํŠธ๋Š” ์†Œํ”„ํŠธ ์‚ญ์ œ(Soft Delete) ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค.
์‹ค์ œ๋กœ DELETE ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๋Š” ๋Œ€์‹  deleted_at ์ปฌ๋Ÿผ์— ์‹œ๊ฐ„์„ ๊ธฐ๋กํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ญ์ œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด, BaseEntity์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ๋‘๊ณ  ์žˆ๋‹ค.

    public void softDelete() {
        this.deletedAt = LocalDateTime.now();
    }

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ญ์ œํ•  ๋•Œ ์‹ค์ œ DB์—์„œ ํ–‰์„ ์ œ๊ฑฐํ•˜์ง€ ์•Š๊ณ , deleted_at ๊ฐ’๋งŒ ์ฑ„์›Œ ๋„ฃ์–ด ๋…ผ๋ฆฌ์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ๋‹ค๊ณ  ํ‘œ์‹œํ•œ๋‹ค.

 

 

๊ทธ๋Ÿฐ๋ฐ ON DELETE CASCADE๋Š” ๋ฌผ๋ฆฌ ์‚ญ์ œ์—์„œ๋งŒ ๋™์ž‘ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.
๋ถ€๋ชจ(Post)๋ฅผ soft delete ํ•ด๋„ DB ์ž…์žฅ์—์„œ๋Š” DELETE๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, Comment๋Š” ๊ทธ๋Œ€๋กœ ๋‚จ๊ณ  CASCADE๋„ ์•„๋ฌด๋Ÿฐ ๋ฐ˜์‘์ด ์—†๋‹ค.


4. ๊ฒฐ๋ก : CASCADE ์ œ๊ฑฐ & ์†Œํ”„ํŠธ ์‚ญ์ œ ๋กœ์ง ์ ์šฉ

๋”ฐ๋ผ์„œ ์™ธ๋ž˜ํ‚ค CASCADE๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ์‚ญ์ œ ์ „ํŒŒ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์—์„œ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

-- CASCADE ๋ถ™์ธ FK ์‚ญ์ œ
ALTER TABLE comments DROP FOREIGN KEY FKh4c7lvsc298whoyd4w9ta25cr;

-- CASCADE ์—†๋Š” FK๋กœ ์žฌ์ƒ์„ฑ
ALTER TABLE comments
  ADD CONSTRAINT FKh4c7lvsc298whoyd4w9ta25cr
  FOREIGN KEY (post_id) REFERENCES posts(id);

 

์ด์ œ DB๋Š” ๋‹จ์ˆœํžˆ ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ๋งŒ ๋ณด์žฅํ•˜๊ณ , ์‹ค์ œ ์‚ญ์ œ ์ „ํŒŒ๋Š” ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ soft delete ๋กœ์ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

    @Transactional
    public void deletePost(Long postId, Long memberId) {
        Post post = postRepository.findById(postId)
                .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));
        if(!post.getMember().getId().equals(memberId)) {
            throw new CustomException(ErrorCode.NO_DELETE_MODIFY_PERMISSION);
        }
        post.softDelete();
        
    // Comment๋„ ์†Œํ”„ํŠธ ์‚ญ์ œ
    List<Comment> comments = commentRepository.findAllByPostId(postId);
    comments.forEach(Comment::softDelete);   
    
    }

5. ์ •๋ฆฌ

- ON DELETE CASCADE → ๋ฌผ๋ฆฌ ์‚ญ์ œ์—์„œ๋งŒ ์œ ํšจ

- Soft Delete๋ฅผ ์“ฐ๋Š” ํ”„๋กœ์ ํŠธ์—์„œ๋Š” CASCADE๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ณ  ์˜คํžˆ๋ ค ํ˜ผ๋ž€๋งŒ ์ดˆ๋ž˜

- ๋Œ€์‹  deleted_at ๊ธฐ๋ฐ˜์œผ๋กœ ์กฐํšŒ๋ฅผ ์ œํ•œ(@Where(clause = "deleted_at IS NULL"))ํ•˜๊ณ ,
- ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๊นŒ์ง€ ์ง์ ‘ soft delete ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ •์„