인프런 커뮤니티 질문&답변

taeu kim님의 프로필 이미지
taeu kim

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

영속성 전이(CASCADE)와 고아 객체

em.remove 동작 안함 이유?

작성

·

778

·

수정됨

0

 

Member.class

package dev.devpool.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private long id;

    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    List<Child> children = new ArrayList<>();

    public List<Child> getChildren() {
        return children;
    }

    public void setChildren(List<Child> children) {
        this.children = children;
    }

    private String name;
    private String nickName;
    private String email;
    private String password;

    public Member() {
    }

    public Member(String name, String nickName, String email, String password) {
        this.name = name;
        this.nickName = nickName;
        this.email = email;
        this.password = password;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }


    public String getName() {
        return name;
    }
    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

Child.class

package dev.devpool.domain;


import javax.persistence.*;

@Entity
public class Child {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Member getMember() {
        return member;
    }

    public void setMember(Member member) {
        this.member = member;
    }
}

로 부모를 Member 자식을 Child로 만들고 delete 시 cascade가 적용되는지를 체크하려 하였습니다.

 

MemberRepository에서 delete 쿼리를 아래와 같이 작성하였습니다.

Test 코드는 아래와 같습니다.

@Test
public void ss() {

    Member member = new Member();
    member.setName("김우");
    member.setEmail("rereers15@naver.com");
    member.setPassword("taeu4");
    member.setNickName("귀요미");

    Child child = new Child();
    child.setMember(member);

    member.getChildren().add(child);

    memberService.join(member);

    em.flush();
    em.clear();

    System.out.println("================");
    memberService.delete(member.getId());
    System.out.println("================");

}

 

 

SQL

 하지만 em.remove 부분이 동작하지 않습니다.

이유가 무엇인가요?

 

** save메서드에서 저장 후 remove()를 하는 것을 실험해보았는데 이 경우는 delete 쿼리까지 정상 출력됩니다.

 

영속성 컨텍스트와 @Transaction과 관련된 이슈인 것같은데 이와 관련해서 설명해주시면 감사하겠습니다.

답변 3

1

taeu kim님의 프로필 이미지
taeu kim
질문자

답변 감사드립니다. 삽질 끝에 JPQL을 사용하면 영속성 컨텍스트가 적용되지 않으므로 참조 무결성 에러가 발생한 것이었다는 것을 알게 되었습니다.

또한 말씀하신 것처럼 마지막의 delete 메서드가 단위 테스트 단위이기때문에 실행하기 전에 rollback 되지 않아 동작하는 것이었습니다.

따라서 em.remove()를 통해 cacade 옵션을 사용하도록 리팩토링하였고 테스트 코드에서는 @Transactional 어노테이션을 제거하였습니다. 이에 따라 test 코드에서 쿼리를 볼 수없다는 문제가 발생하였는데

TransactionalTemplate를 사용하여 EntityManager의 메소드도 추가로 사용할 수 있도록 구현하여 문제를 해결하였습니다. 감사합니다.

해결과정에 대해서도 공유해주셔서 진심으로 감사합니다.
파이팅입니다!

1

taeu kim님의 프로필 이미지
taeu kim
질문자

https://drive.google.com/file/d/1v82ie2dKlRa6uJCsuKAoza3760LDl6r5/view?usp=share_link

답변감사드립니다. Test 단에서의 @Transaction은 Application에서의 @Transaction과 좀 다른가 보군요 이부분에 대해서 추가로 공부해보겠습니다.

실제로 Application 단에서 실행하면 @em.remove가 정상적으로 동작하네요

MemberServiceTest의

 

2.@Test

ss()

를 실행하면됩니다.

 

  1. @Transaction이 어디에 위치해야하는지 모르겠습니다. Repository 인지 Service인지

  2. 정확히 @Transaction이 어떤 역할을 하는지

앗 ㅎㅎ 정상적으로 안되실 경우를 대비해서 작성하셨던 코드를 달라고 말씀드렸던 것입니다 ㅎㅎ

그리고 추가로 주셨던 질문에 대해서 읽어보니 2개의 질문 모두 @Transactional에 대해서 이해하지 못하고 계셔서 발생하는 질문 같습니다. 정말 간단하게 설명드리면 @Transactional은 해당 메서드에 트랜잭션을 걸어주는 것입니다. 즉 트랜잭션이 필요한 곳에 사용하시는 어노테이션입니다. 이 내용은 DB에 대한 학습도 필요한 부분입니다. 영한님의 스프링 DB 1편 - 데이터 접근 핵심 원리, 스프링 DB 2편 - 데이터 접근 활용 기술 강의를 들으시면 언제, 어디에 트랜잭션을 걸어줘야 할지는 확실하게 감이 오실 거라 생각합니다!

 

0

안녕하세요. taeu kim님, 공식 서포터즈 y2gcoder입니다.

강의 코드와 구조가 다르게 하신 것 같아 MemberRepository, MemberService, 해당 테스트의 전체 코드가 필요합니다. 해당 코드 없이 추측을 해보자면 테스트 코드가 있는 클래스에 @Transactional이 붙어있다면 마지막 MemberService 의 delete()에서 만들어져 쓰기지연 SQL 저장소에 적재된 delete 쿼리는 테스트에서의 @Transactional 때문에 날아가지 않는 것이 맞습니다. 기본적으로 @SpringBootTest를 붙인 통합 테스트에서 @Transactional을 붙이면 테스트를 종료하면서 테스트에서 했던 사항을 다 롤백하는 기능을 갖고 있습니다. 이 기능 + JPA의 쓰기 지연 SQL 저장소가 더해져 위의 테스트 결과가 나타나게 됩니다.

MemberService의 join()에서 사용하는 것으로 추측되는 MemberRepository의 save() 에서 em.remove()를 추가했을 때는 delete 쿼리가 날아간 것은 memberService.join() 이후 바로 em.flush()를 해줬기 때문입니다. 이 부분이 이해가 가지 않으신다면 본 강의의 플러시 부분을 다시 학습해보시길 권해드립니다.

위에서 말씀드렸던 것과 다르게 코드가 작성되어 있다면

전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.
구글 드라이브 업로드 방법은 다음을 참고해주세요.

구글 드라이브 업로드 방법 링크

주의: 업로드시 권한 문제 꼭 확인해주세요

추가로 다음 내용도 코멘트 부탁드립니다.

1. 실행 방법을 알려주세요.
2. 어떻게 문제를 확인할 수 있는지 자세한 설명을 남겨주세요.

감사합니다.

taeu kim님의 프로필 이미지
taeu kim

작성한 질문수

질문하기