(spring) (자바 ORM 표준 JPA 프로그래밍 - 기본편_03)

전성영·2022년 6월 8일
0

spring

목록 보기
14/31

바로 시작~~!

Member 와 Team은 N:1 이다.
Member를 가져와야 할 때 Team의 데이터도 같이 가져온다.
물론 Team 테이블도 사용할 때는 좋지만 사용하지 않을때는 낭비지 않을까?
그래서 JPA에서는 이것을 프록시, 지연로딩으로 잡는다고 한다.

프록시란?

실제 엔티티 객체 대신에 사용되는 객체이다.

em.find() - 데이터베이스를 통해서 실제 엔티티 객체 조회.
em.getReference() - 데이터베이스 조회를 미루는 가짜(프록시)
엔티티 객체 조회.
DB에 쿼리가 안나가지만 객체는 주어지는 것.

Member findMember = em.find(Member.class, member.getId());
System.out.println(findMember.getId());
System.out.println(findMember.getUsername());
Member findMember = em.getReference(Member.class, member.getId());
System.out.println(findMember.getId());
System.out.println(findMember.getUsername());

아래 코드는 getReference() 하는 시점에서 쿼리를 쏘지 않는다.
이유는 getReference()를 사용하면 이 값이 사용되는 시점에서 쿼리를 쏘기 때문이다.

또한 두 번째 줄 .getId()에서도 쿼리를 쏘지 않는다.
이유는 첫 번째 줄에서 이미 파라미터로 getId()를 사용했기 때문이다.

여기서 findMember는 hibernate가 만든 가짜 클래스이다.
이것이 프록시 클래스이다.

프록시 특징

1. 실제 클래스를 상속 받아서 만들어진다.
2. 실제 클래스와 겉 모양이 같다.

getId() 를 호출하면 실제 target에 있는 getId()를 대신 호출한다.

처음엔 사진과 같이 target이 비어있다.

그럼 어떻게 동작할까?????

프록시 객체인 member를 가져온다.
이 상황에서 getName()을 호출하면??????????

처음엔 아까 말했듯이 이 친구가 찾아갈 곳이 적혀있는 target이 null이다.

그렇게 되면 영속성 컨텍스트에 요청을 한다.
그 후 영속성 컨텍스트가 DB를 조회한 후 Member에게 Entity를 생성해준다.

생성 후 Proxy 안 targete이랑 진짜 Member랑 연결시켜준다.

3. 프록시 객체는 처음 사용할 때 한 번만 초기화
4. 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
5. 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)

6. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환

Member m1 = em.find(Member.class, memeber1.getId());
m1.getClass() 출력~

Member reference = em.getReferance(Member.class, memeber1.getId());
reference.getClass() 출력~

reference.getClass() 를 해주면 proxy객체가 나와야 할 거 같지만?? Member객체로 나온다.

이유는 이미 영속성 컨텍스트 안에 올라와있는데 굳이 proxy 할 이유도 없고, 성능도 원본 객체를 반환하는 게 좋기 때문?? 이다.
Jpa에서는 같은 영속성 컨텍스트 안이고, PK값이 같은 상태에서
a == a 를 하면 무조건 true
가 되어야 한다.

그럼 그 반대는?

Member reference = em.getReferance(Member.class, memeber1.getId());
m1.getClass() 출력~

Member m1 = em.find(Member.class, memeber1.getId());
reference.getClass() 출력~

Jpa에서는 같은 영속성 컨텍스트 안이고, PK값이 같은 상태에서
a == a 를 하면 무조건 true
가 되어야 한다. 라고 했다.
그럼 referance는 proxy객체니깐 m1도 proxy를 반환해준다. 신기방구..

7. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생(하이버네이트는org.hibernate.LazyInitializationException 예외를 터트림)

Member refMember = em.getReference(Member.class, member.getId());
System.out.println("refMember = " + refMember.getClass());

em.close(); //em.detach(refMember);

System.out.println("refMember = " + refMember.getUsername());

em.close()나 em.detach() 등 영속성 컨텍스트를 깨끗하게 해준 후 proxy를 가져오려고 하면 could not initialize proxy 에러가 발생한다.
실무에서 자주 접하는 에러라고 한다. 주의하자!!

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
emf.getPersistenceUnitUtil().isLoaded(refMembber)

.getUserName()으로 초기화 하기 전엔 false, 해준 후엔 true 반환

  • 프록시 클래스 확인 방법
    .getClass() 로 찍어본다.
    ex) refMember.getClass()
  • 프록시 강제 초기화
Hibernate.initialize(refMember); 

• 참고: JPA 표준은 강제 초기화 없음
강제 호출: refMember.getName()

즉시로딩과 지연로딩

지연로딩(Lazy)

Member.java
(fetch = FetchType.LAZY) team 을 프록시 객체로 줘야한다.
즉 맴버 클래스만 DB에서 조회를 한다.

예제를 보자.


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

Member member = new Member();
member.setUsername("qwe");
member.setTeam(team);

em.persist(member);

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

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

System.out.println("refMember = " + m.getTeam().getClass());

System.out.println("=========");
m.getTeam().getName();
System.out.println("=========");

Memebr를 우선 가져오고, Team의 속성이 사욜될 때 프록시 객체가 초기화 되면서 DB에서 값을 가져온다.(Team에 대한 쿼리를 던진다.)

즉 이것이 지연로딩이다.


System.out.println("m = " + m.getTeam().getClass());

m은 프록시 객체이다.

만약 System.out.println("m = " + m.getClass()); 라고 했다면
Member 객체일 것이다.


정리

Member와 Team이 지연로딩으로 Setting이 되어있다.
그렇게 되면 Member를 받아올 때 Team을 가짜 프록시 객체로 받아서 놓는다.

그 후 실제 team 속성을 사용하는 시점에 프록시가 초기화 되면서 쿼리가 나간다.


즉시로딩(Eager)

지연로딩은 Member와 Team이 연관관계가 있는데, Team을 거의 안쓸 때 사용했다.
하지만 Team도 같이 자주 쓰인다면?? 그럴 때 즉시로딩이 필요하다.


LAZY -> EAGER

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

Member member = new Member();
member.setUsername("qwe");
member.setTeam(team);

em.persist(member);

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

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

System.out.println("m = " + m.getTeam().getClass());

System.out.println("=========");
m.getTeam().getName();
System.out.println("=========");

m.getTeam().getClass()) 가 프록시 객체로 나왔던 LAZY와는 달리

Team 객체로 나오고, Member와 Team을 동시에 가져오는 것을 볼 수 있다.

이것이 즉시로딩이다.


정리

즉시로딩시, Member를 조회하면 Team도 같이 조회를 한다.

  • 실무에서는 가급적 지연 로딩만 사용
    왜? 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생한다.
  • 즉시 로딩은 JPQL에서 N+1 문제를 발생시킨다.
  • @ManyToOne, @OneToOne 은 기본이 즉시 로딩이다. 그래서 LAZY 설정을 해줘야 한다.
    @OneToMany 는 기본값이 LAZY

영속성 전이(CASCADE)

cascade = CascadeType.ALL 이 선언되어 있다면,
parent 를 persist 할 때 그 밑에 있는 애들도 persist를 날려주는 것이다.

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

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

em.persist(parent);
em.persist(child1);
em.persist(child2);

원래 세 번 persist를 해줘야 할 것을 cascade 해주면
em.persist(parent); parent만 persist 해줘도 DB에 저장이 된다.

영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음

CASCADE의 종류

ALL: 모두 적용
PERSIST: 영속
이 두 가지를 많이 쓴다.


고아객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 일컫는 말이다.

orphanRemoval=true 를 적용하고,

Parent findParent = em.find(Parent.class, parent.getId())
findParent.getChildList().remove(0)

하면 그 밑에 있는 데이터들도 다 삭제가 된다.

orphanRemovalcascade=CascadeType.REMOVE 차이는 뭐지?

부모 엔티티가 삭제되면 자식 엔티티도 전부 삭제되는 것은 동일하지만 원인이 다르다.

  • orphanRemoval = true 옵션은 부모 엔티티가 사라지면서 자식 엔티티와의 참조(연결)가 끊어져서 삭제된다.
  • CascadeType.REMOVE 옵션은 원래 엔티티가 삭제될 때 연관된 엔티티를 전부 삭제하는 옵션이다.
    orphanRemoval 옵션은 Collections 에서 자식 엔티티를 삭제하는 걸로 DB 에서도 삭제 가능하지만 cascade 는 불가능

!주의할 점

  • 참조하는 곳이 하나일 때 사용해야한다.
  • 특정 엔티티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 가능

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

CascadeType.ALL + orphanRemovel=true

• 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화,
em.remove()로 제거
• 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명
주기를 관리할 수 있음
• 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때
유용

헷갈리지 마

@ManyToOne, @OneToOne 의 기본은 즉시로딩!!!
(fetch = FetchType.LAZY) 해주자!!!

고아객체 삭제는 @OneToOne, @OneToMany만 가능!!!

profile
Slow and Steady

0개의 댓글