woowacourse-study / 2022-jpa-study

🔥 우아한테크코스 4기 JPA 스터디 (22.06.13~22.07.02) 🔥
5 stars 1 forks source link

[섹션 8] 토르 제출합니다 #20

Closed injoon2019 closed 2 years ago

injoon2019 commented 2 years ago

이번 섹션 화두: Member를 조회할 때 Team도 조회해야할까? Team과 관련된 데이터나 로직은 안써도?

프록시

프록시 특징

프록시 기초

em.find()를 하면 데이터베이스를 통해서 실제 엔티티 객체를 조회한다. em.getReference()를 하면 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체를 조회한다.

    Member findMember = em.getReference(Member.class, member.getId());
    System.out.println("findMember.id = " + findMember.getId());
    System.out.println("findMember.username = " + findMember.getUsername());

그래서 위의 코드에서 findMember.getId()할 때는 쿼리가 안나가는데 findMember.getUsername()에는 쿼리가 나간다.

프록시 객체의 초기화

Member member = em.getReference(Member.class, "id1");
member.getName();

위의 코드를 실행한다고 생각해보자.

  1. member.getName()은 프록시에게 요청된다.
  2. 프록시는 영속화 컨텍스트에 초기화 요청을 한다.
  3. 영속성 컨텍스트는 DB에서 조회를한다
  4. DB에서 조회한 데이터로 실제 객체를 생성해주고 프록시의 타겟과 요청해준다.

프록시의 특징

즉시 로딩과 지연 로딩

지연 로딩 LAZY를 사용해서 프록시로 조회

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USERNAME")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    ...

fetch = FetchType.LAZY 옵션을 줬기 때문에 프록시로 조회한다.

만약 비즈니스 로직에서 team을 거의 안쓴다면 프록시를 이용하는 것이 좋고, team을 자주 쓴다면 결국 쿼리가 두 번 따로 나가니 손해다.

즉시로딩

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USERNAME")
    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    ...

이렇게 하면 JOIN을 이용해서 한번에 다 조회한다.

프록시와 즉시로딩 주의

실무에서 즉시 로딩은 사용하면 안된다 JPQL에서 N+1문제를 일으킨다

TypedQuery<Member> result = em.createQuery("select m from Member m", Member.class)
    .getResultList();

이 쿼리로 Member 리스트를 가져오려고 했는데, Member의 개수가 N개라 할때 N번만큼 각각 팀을 가져오는 쿼리가 나가는 것.

XtoOne은 디폴트가 eager니 신경써서 LAZY로딩을 하자.

영속성 전이(CASCADE)

앞에서 나완 연관관계등이랑 전혀 관계없다

영속성 전이 : CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때

@Entity
public class Parent {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> childList = new ArrayList<>();

    ...
}

Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);

tx.commit();

parent만 persist 했는데 child들도 다 persist된다. 소유주가 하나일 때만 의미가 있다. 만약 다른 객체에서도 child를 아는 경우에는 안쓰는게 좋다. 운영이 힘들다.

CASCADE 종류

고아 객체

고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 엔티티를 자동으로 삭제해주는 것이다. 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 것이다.

참조하는 곳이 하나일 때 사용해야 한다

영속성 전이 + 고아 객체, 생명주기

CascadeType.ALL + orphanRemoval=true 이 두 옵션을 모두 주면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있는 것이다.

헷갈렸던 부분: 영속성 전이에서 remove 옵션 vs orphanRemoval 영속성 전이에서 remove는 부모가 제거되면 자식의 영속성도 제거하는 것? orphanRemoval은 부모에서 제거되면 부모 엔티티에서 제거되면 자식 엔티티를 삭제하는 것

injoon2019 commented 2 years ago

궁금했던 것: 영속성 전이 CASCADE에서 remove랑 orphanRemoval = True는 어떻게 다르게 동작하는가? CASCADE remove는 부모를 삭제하면 자식까지 다 삭제하는 것. orphanRemoval=True는 부모와 참조가 끊어진 것을 다 지우는 것.

부모가 지워지면 자식과의 참조가 끊어지기 떄문에 결국 두 경우 모두 자식까지 다 지워진다.

injoon2019 commented 2 years ago

https://tecoble.techcourse.co.kr/post/2021-08-15-jpa-cascadetype-remove-vs-orphanremoval-true/