Spring Boot/Spring Boot JPA-기본편 강의 정리(김영한)

[SpringBoot-JPA 기본편] 자바 ORM 표준 JPA 프로그래밍 - 기본편: 8. 프록시와 연관관계 관리

조찬국 2024. 2. 26. 02:20
728x90

강의 출처:
https://www.inflearn.com/course/ORM-JPA-Basic#curriculum

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런

저는 야생형이 아니라 학자형인가봐요^^ 활용편 넘어갔다 30% 정도 듣고 도저히 답답해서 기본편을 들어버렸네요^^. 한주 한주 김영한님 강의 들으니 렙업되는 모습을 스스로 느낍니다. 특히 실

www.inflearn.com

 

해당 강의는 Inflearn에 등록된 김영한님의 Spring Boot 강의입니다.

이번시간에는 프록시와 연관관계 관리에 대해서 공부하자.


1. 프록시

프록시

  • em.find() : DB를 통해서 실제 엔티티 객체 조회
  • em.getReference() : DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회

[프록시 특징]

  • 실제 클래스를 상속받아 만들어짐 (타입 체크 시 주의, '==' 비교는 실패함, 'instance of' 사용할 것)
  • 실제 클래스와 겉 모양이 같음.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨 (이론상)
  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생함. (hibernate는 org.hibernate.LazyInitializationException 예외를 터트림)

다행히도 JPA 표준에는 강제 초기화가 없다.

2. 즉시로딩과 지연로딩

멤버를 조회할 때 팀 정보는 필요 없을때 Team을 불러올 필요가 있을까? 아마 필요 없을 것이다.

즉시 로딩(EAGER)

그러면 즉시로딩을 설정을 위해 Member 객체에 다음과 같이 EAGER로 설정하자.

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID") //참조할 객체에 TEAM_ID로 참조 할것임을 annotaion
private Team team; //참조할 객체의 객체 변수 선언. 양방향 매핑시에 사용가능

즉시 로딩은 Member 조회시 Member와 연관된 객체들을 한번에 조인해서 가져온다.

다음과 같은 코드를 main함수의 try문 안에 넣자.

       	Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Team team2 = new Team();
            team2.setName("TeamB");
            em.persist(team2);


            Member member = new Member();
            member.setName("user1");
            member.setTeam(team);
            em.persist(member);

            Member member2 = new Member();
            member2.setName("user2");
            member2.setTeam(team2);
            em.persist(member2);

            em.flush(); //영속성 콘텍스트 변경사항 적용 후 조회시 쿼리 확인을 위한 코드
            em.clear(); //flush 후 영속성 콘텍스트 초기화

            Member m = em.find(Member.class, member.getId());

            System.out.println("==============");
            //즉시로딩EAGER 타입을 사용하여 Member와 Team을 함께 조회(둘이 같이 자주 조회하는 경우)
            System.out.println("teamName= "+ m.getTeam().getName()); //실제 영속성컨테스트 초기화-> DB조회
            System.out.println("==============");;

 

왼쪽이미지: 위 코드 실행 결과

실제로 위 코드를 실행 해보면 team을 조인해서 한방에 갖고 온다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

그러며 실무에서는 과연 즉시 로딩이 좋은가?

 

멤버는 사실 여러명의 회원이 존재한다. 만약 아래와 같이 회원 객체 전부를 목록으로 띄우게하는 비즈니스 로직이 있다면, 즉시 로딩은 Member와 연관된 모든 객체들을 조회하게된다.

이렇게 1개의 쿼리를 날렸는데 N개의 쿼리가 날라가는 현상을 1+N 이라고 한다.

(추후 JPA 활용편2에서 성능 최적화에서 이 N+1 해결 방법을 설명하겠습니다.)

List<Member> members=em.createQuery("select m from Member as m",Member.class)
        .getResultList();

지연로딩(LAZY)

Member에서 LAZY로만 바꿔보고 위의 즉시로딩과 같은 코드들을 실행 해보자.

정리

 

X To One**은 반드시 지연로딩(LAZY)으로 세팅하자!**

3. 영속성 전이: CASCADE

parent

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

    private String name;

    //★★★영속성 전이 CASCADE-> MAIN에서 child를 굳이 persist 안해도됨★★★
    //★★★고아 객체: 부모와 연관관계가 끊어지면 자동으로 DELETE 쿼리를 보냄.
    // fasle: 그냥 영속성에서만 지움. 데베에서 지우진 않음.
    @OneToMany(mappedBy = "parent",cascade = CascadeType.ALL,orphanRemoval = true)
    private List<Child> childList=new ArrayList<>();

    //★★★연관관계 편의 메소드★★★
    public void addChild(Child child){
        childList.add(child);
        child.setParent(this);
    }
     //getter,setter

 }

Child 객체

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="parent_id")
    private Parent parent;
    private String name;
    //getter,setter

 }

4. 고아 객체

main함수

//영속성 전이 사용 전
Child child1= new Child();
Child child2= new Child();

Parent parent=new Parent();
parent.addChild(child1); //양방향 매핑
parent.addChild(child2); //양방향 매핑

em.persist(parent);
// 부모만 persist하여  child 엔티티들을 모두 persist 안해도된다.
//em.persist(child1);
// em.persist(child2);

em.flush(); //영속성 콘텍스트 변경사항 적용 후 조회시 쿼리 확인을 위한 코드
em.clear(); //flush 후 영속성 콘텍스트 초기화

//고아객체 생성
Parent findParent = em.find(Parent.class, parent.getId());
//추가적으로 고아객체를 지우면 데베값도 자동으로 지우게 해준다.
findParent.getChildList().remove(0);//★★첫번째 자식을 "데베에서" 지우기

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

이렇게 영속성 전이 + 고아객체 생성을 통해 부모 엔티티릍 통해 자식의 생명주기를 관리 할 수 있게된다.

 

결과

728x90