inflearn logo
강의

Course

Instructor

Java ORM Standard JPA Programming - Basics

Persistence Propagation (CASCADE) and Orphan Object

cascade=ALL, orphanRemoval=true 관련 질문

Resolved

653

kimsy

7 asked

2

안녕하세요.

혼자 고민하고 생각 해보다 해결이 되지 않아 문의드리러 왔습니다 ㅠㅠ

해당 수업을 듣고, 사이드 프로젝트를 수행하면서 orphanRemoval을 사용해보게 되었습니다.

대략적인 엔티티간의 관계는 다음과 같습니다.

총 4개의 엔티티가 각각 다대일 매핑으로 이루어져 있는데요.

각 엔티티의 정보는 다음과 같습니다.

문제는 엔티티 B까지만 cascade=ALL, orphanRemoval=true가 적용된다는 것입니다..ㅠㅠ

차이가 있다면, 엔티티 A는 유저의 정보라서, 항상 저장이 되어있는 것을 꺼내어와서 사용한다는 것이고(저장을 회원가입 로직에서만 수행합니다.),
엔티티 B, C, D는 글 작성 시 모두 새로 생성하여 사용한다는 점입니다.

저는 엔티티B 삭제 로직에서,
엔티티A의 List에서 엔티티B를 remove 하면 B, C, D가 모두 삭제되는 것을 기대했는데, 전혀 삭제가 되지 않습니다 ㅠ (List에서 엔티티B가 제거는 됩니다..)

오히려 엔티티A의 List에서 엔티티B을 remove하고, 추가적으로 엔티티B의 리포지토리에서 엔티티 B를 삭제해야만 비로소 삭제가 완료됩니다.

기능 자체를 구현은 했지만, 이렇게 동작하는 원인을 알고싶어서, 여기저기 찾아보다가 답을 찾지 못해 이곳에 질문 글을 남기게 되었습니다 ㅠㅜ

관련 코드는 아래 깃허브 리포지토리에 있습니다.

엔티티 리포지토리

https://github.com/SeolYoungKim/siders/tree/oauth2login/src/main/java/com/example/siderswebapp/domain

삭제 로직 리포지토리

https://github.com/SeolYoungKim/siders/tree/oauth2login/src/main/java/com/example/siderswebapp/service/post

jpa JPA java

Answer 2

2

yh

안녕하세요. 딱구님

제가 다음과 같이 테스트 코드를 만들어 돌려보았을 때는 모두 정상 동작하는 것을 확인할 수 있었습니다.

다음 코드를 참고해주세요.

package com.example.siderswebapp;

import com.example.siderswebapp.domain.Ability;
import com.example.siderswebapp.domain.fields.Fields;
import com.example.siderswebapp.domain.member.Member;
import com.example.siderswebapp.domain.member.RoleType;
import com.example.siderswebapp.domain.post.Post;
import com.example.siderswebapp.domain.tech_stack.TechStack;
import com.example.siderswebapp.service.post.PostService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.util.List;

@Transactional
@SpringBootTest
class SidersWebappApplicationTests {

    @Autowired
    PostService postService;

    @Autowired
    EntityManager em;

    @Test
    void jpaOnlyTest() {
        Member member = initData();

        Member findMember = em.find(Member.class, member.getId());
        findMember.getPostList().remove(0);
        em.flush();
    }

    @Test
    void serviceTest() throws IllegalAccessException {
        Member member = initData();
        Long postId = member.getPostList().get(0).getId();
        postService.deletePost2(postId, member.getAuthId());
        em.flush();
    }

    private Member initData() {
        Member member = Member.builder()
                .email("email")
                .authId("id")
                .picture("pic")
                .name("name")
                .roleType(RoleType.ADMIN)
                .build();

        Post post = Post.builder()
                .member(member)
                .title("title")
                .build();

        Fields fields = Fields.builder()
                .fieldsName("name")
                .recruitCount(1)
                .totalAbility(Ability.HIGH)
                .post(post).build();

        TechStack techStack = TechStack.builder()
                .stackName("stack")
                .fields(fields)
                .build();

        em.persist(member);
        em.persist(post);
        em.persist(fields);
        em.persist(techStack);

        em.flush();
        em.clear();
        return member;
    }

}

 

    public void deletePost2(Long id, String authId) throws IllegalAccessException {

        Post post = postRepository.findById(id)
                .orElseThrow(PostNotExistException::new);

        Member member = memberRepository.findByAuthId(authId)
                .orElseThrow(IllegalAccessException::new);

        member.getPostList().remove(post);
//        postRepository.delete(post); 코드 제거
    }

 

실행 로그

Hibernate: 
    delete 
    from
        tech_stack 
    where
        tech_stack_id=?
Hibernate: 
    delete 
    from
        fields 
    where
        fields_id=?
Hibernate: 
    delete 
    from
        post 
    where
        post_id=?

0

kimsy

ㅠㅠ 직접 해봐주셔서 너무 감사드립니다.

제가 테스트 코드에 갇혀서, 포스트맨으로 2차 검증을 해 볼생각을 못했네요..

선생님께서 문제가 없이 작동한다는 것을 보고, 제 테스트 코드가 잘못 되었음을 깨닫고, 포스트맨으로는 삭제가 되는지 1차적으로 먼저 확인을 해봤습니다. 그랬더니 삭제 기능이 잘 작동 되네요..

한동안 디버깅도 해보고 이래저래 테스트코드도 바꿔보고 하다가 고민 끝에 질문을 드렸었던 것인데, 테스트 코드가 잘못되었을 것이라고는 정말 상상도 못했습니다 ㅠㅠ 너무 부끄럽네요..

이제는 선생님께서 작성해주신 테스트코드 기준으로 재작성을 해봐야겠습니다.

이번 경험을 토대로 테스트 코드에 오류가 있을 수도 있음을 항상 생각해야겠습니다..

바쁘신 와중에(심지어 휴일인데..ㅠㅠ) 시간내어 피드백 주셔서 정말 감사드립니다.

 

-- 해결 했습니다! --

Test 코드에서 선생님처럼 memberRepository.save()의 위치를 엔티티들의 아래쪽으로 내렸더니 해결이 되었습니다.

아직은 Member를 저장 하고, Post에 할당하는 것과 모든걸 할당하고 마지막에 한번에 저장하는 것의 차이를 잘 모르겠습니다.

이러한 현상이 발생하는 이유를 몰랐던 것은, 아무래도 제가 JpaRepository의 동작 원리와 영속성 컨텍스트 등 JPA 관련 개념을 충분히 몰랐기 때문이라고 생각이 듭니다. 이 기회에 한번 더 복습하면서 이러한 현상이 발생했던 이유에 대해 깨달아 보도록 해야겠습니다.

다시한번 시간내어 확인해주셔서 감사드립니다.

1

kimsy

하나 의심이 되는건, 생성 시점이 다르기 때문일 것 같은데, 혹시 해당 이유일까요..?
영한쌤이 말씀하신 aggregate root 를 찾아보니 생성 시점에 따라 나눈다고 설명이 되어있어서, 그부분을 의심하고 있습니다.
저는 뭔가 추상적인 개념일 뿐이라고 생각했는데, JPA에서도 실제로 aggregate root 개념이 적용되어서, 각자 생성 시점이 다르면 해당 방법으로 관리가 되지 않는건가요??

몇 가지 테스트를 좀 더 해봤는데요.

  • 엔티티A를 "삭제" 할 경우에는 엔티티B, C, D가 모두 삭제됩니다.

    • 하지만 여전히 엔티티 A의 List에서 엔티티 B를 "remove"할 경우에는 삭제가 되지 않습니다.

     

이러한 결과를 미루어보았을 때, cascadeType=ALL만 제대로 작동한다고 봐도 될까요?

그렇다면 orphanRemoval은 왜 작동하지 않는걸까요..!

실무 조언 관련 질문입니다.

0

38

1

H2데이터베이스 파일 생성

0

45

2

서브쿼리 강의에서 ALL 예시 관련 질문드립니다.

0

47

2

수정또는 삭제시 영속성 엔티티에 값이 무조건 있어야 하나요?

0

45

1

JPQL 메소드와 락

0

47

1

Delivery @OneToOne

0

54

1

17강 4~5분대 테이블 값 조회가 안됩니다.

0

85

2

UnsupportedOperationException 발생

0

80

3

H2 Database 연결이 안됩니다.

0

86

2

연관관계 매핑 질문드립니다.

0

77

2

h2데이터베이스 실행오류

0

103

2

persistence.xml

0

101

2

양방향 연관관계에서 연관관계의 주인(mappedBy)을 왜 꼭 정해야 하나요?

0

74

1

영속성 컨텍스트

0

59

1

JPA 프록시

0

86

1

Native Query와 MyBatis

0

61

1

영속성 컨텍스트는 어떤 메모리에 저장되는건가요?

0

81

1

임베디드 타입 예시 코드 관련 질문

0

110

3

명시적 조인에서 별칭을 주면 왜 객체에 접근할 수 있나요

0

89

3

인텔리제이 패키지 커서 단축키 질문

0

100

2

혹시 현재는 ID 데이터 타입이 String이면 안되나요?

0

133

1

양방향 연관관계 시 연관관계 주인을 설정하는 이유

0

67

1

임베디드 타입과 MappedSuperClass의 차이점이 궁금합니다.

0

95

1

데이터베이스가 초기화되는 것 같아요

1

172

2