JPA 스터디 (8장)

14300 단어 studyJPAJPA

8장 프록시와 연관관계 관리

8.1 프록시

  • 엔티티가 실제 사용될때 까지 데이터베이스 조회를 지연 하는 방법을 지연 로딩 이라고 한다.

  • 지연 로딩 기능을 사용 하려면 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이때 프록시 객체를 사용 한다.

  • 프록시 기초

    • EntityManager.getReference() 메소드를 사용 하면 된다.
    • 이 메소드 호출 시 JPA는 데이터베이스 조회를 하지 않고 실제 엔티티 객체도 생성 하지 않는다. 대신 데이터베이스 접근을 위임한 프록시 객체를 반환함
  • 프록시 초기화 과정

    • 프록시 객체에 실제 엔티티 값을 호출하여 실제 데이터를 조회
    • 실제 엔티티가 생성되어 있지 않을 시 영속성 컨텍스트에 실제 엔티티 생성 요청
    • 영속성 컨텍스트는 데이터베이스 조회 후 실제 엔티티 객체 생성
    • 프록시 객체는 생성된 실제 엔티티 객체의 참조를 멤버변수로 보관
  • 프록시 특징

    • 프록시 객체는 처음 사용할 때 한 번만 초기화 된다.
    • 프록시 객체 초기화 시 프록시 객체가 실제 엔티티로 바뀌는건 아님( 프록시 객체가 초기화 되면 프록시 객체를 통해 실제 엔티티에 접근할 수 있어짐)
    • 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시 주의 해야 한다.
    • 영속성 컨텍스트에 찾는 엔티티가 이미 존재할 시 em.getReference() 메소드 호출 해도 프록시가 아닌 실제 엔티티를 반환 한다.
    • 초기화는 영속성 컨텍스트의 도움을 반드시 받아야 하므로 준영속 상태의 프록시를 초기화 시 문제 발생(하이버네이트의 경우 LazyInitializationException 예외 발생 시킴)
  • 프록시와 식별자

    • 프록시 객체는 PK 값을 보관하고 있기 때문에 엔티티 접근 방식이 프로퍼티(@Access(AccessType.PROPERTY) )일 경우 식별자 값을 조회하는 메소드를 호출 해도 프록시 초기화가 되지 않음
    • 연관관계 설정 시 식별자 값만 이용하므로 프록시를 사용 시 데이터베이스 접근 횟수를 줄일 수 있다.
  • 프록시 확인

    • PersistenceUnitUtil.isLoaded(Object entity) 메소드 사용 시 프록시 인스턴스의 초기화 여부를 확인할 수 있다.

      boolean isLoad = em.getEntityManagerFactory()
      		.getPersistenceUnitUtil().isLoaded(entity);
    • 조회한 엔티티가 진짜 엔티티 인지 프록시 인지 확인 시 클래스명을 직접 출력 해봄

      System.out.println("memberProxy = "+ member.getClass().getName());
      //결과 : memberProxy = jpabook.domain.Member_$$_javassist_0

8.2 즉시 로딩과 지연 로딩

  • 즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회 한다.

    • 설정 방법 : @ManyToOne(fetch = FetchType.EAGER)
  • 지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회 한다.

    • 설정 방법 : @ManyToOne(fetch = FetchType.LAZY)
  • 즉시 로딩

    • @ManyToOne(fetch = FetchType.EAGER) 로 지정 한다.

      @Entity
      public class Member {
      	
      	@ManyToOne(fetch = FetchType.EAGER)
      	@JoinColumn(name = "TEAM_ID")
      	private Team team;
      	...
      }
    • 즉시 로딩 시 회원과 팀 두 테이블을 조회해야 하는데, 쿼리를 두번 실행하지는 않고 대부분의 JPA 구현체들은 최적화를 위해 가능하면 조인 쿼리를 사용 함

      [즉시 로딩시 사용된 조인 쿼리]
      
      SELECT 
      	M.MEMBER_ID AS MEMBER_ID,
      	M.TEAM_ID AS TEAM_ID,
      	M.USERNAME AS USERNAME,
      	T.TEAM_ID AS TEAM_ID,
      	T.NAME AS NAME
      FROM
      	MEMBER M **LEFT OUTER JOIN TEAM** T
      		ON M.TEAM_ID = T.TEAM_ID
      WHERE
      	M.MEMBER_ID = 'member1';
    • 내부 조인(INNER JOIN)이 아닌 외부 조인 (LEFT OUTER JOIN)을 사용 한 것을 유심히 봐야 한다.

    • 외부 조인을 사용한 것은 현재 TEAM_ID 는 NULL 값을 허용하고 있기 때문에 팀에 소속되지 않은 회원도 있을 가능성이 있기 때문에 외부 조인이 사용 된 것이다.

    • INNER JOIN이 성능, 최적화에 더 유리 한데 이를 사용하려면?

      • 외래 키에 NOT NULL 제약 조건을 설정 후 JPA에 알려주면 된다.
      • @JoinColumn(name = "TEAM_ID", nullable = false)
      • 혹은 @ManyToOne(fetch = FetchType.EAGER, optional = false)
      • JPA는 선택적 관계일 때 외부 조인 이용, 필수관계 시 내부 조인 이용함
  • 지연 로딩

    • @ManyToOne(fetch = FetchType.LAZY) 로 지정 한다.

      @Entity
      public class Member {
      	
      	@ManyToOne(fetch = FetchType.LAZY)
      	@JoinColumn(name = "TEAM_ID")
      	private Team team;
      	...
      }
    • 8.1장 프록시 내용에서 언급했듯이 지연 로딩은 프록시를 사용하며 사용되는 프록시 객체는 실제 사용될 때 까지 데이터 로딩을 미룬다.

    • (참고) 조회 대상이 영속성 컨텍스트에 이미 있다면 프록시 객체를 사용할 이유가 없다. 따라서 이미 로딩 되어 있으면 실제 엔티티를 사용 한다.

8.3 지연 로딩 활용

  • JPA 기본 Fetch 전략

    • @ManyToOne, @OneToOne : 즉시 로딩 (FetchType.EAGER)
    • @OneToMany, @ManyToMany : 지연 로딩 (FetchType.LAZY)
    • 기본 전략은 연관된 엔티티가 하나면 즉시로딩, 컬렉션이면 지연 로딩을 사용 한다.
      • 컬렉션의 경우 즉시로딩 시 해당 회원 로딩 시 수만건의 데이터가 함께 로딩될 수 있음
    • 추천 하는 방법은 모든 연관관계에 지연 로딩을 사용 하는 것이다?!
      • 애플리케이션 개발이 어느정도 완료단계에 왔을 때 실제 사용하는 상황을 보고 꼭 필요한 곳에만 즉시로딩을 사용하도록 최적화
  • 컬렉션에 즉시로딩 사용시 주의 점

    • 컬렉션을 하나 이상 즉시 로딩하는 걸 권장하지 않음
    • 컬렉션의 즉시로딩의 경우 항상 외부 조인을 사용 한다. JPA는 일대다 관계를 즉시 로딩 할 때 항상 외부 조인을 사용
    • @ManyToOne, @OneToOne
      • optional = false : 내부 조인
      • optional = true : 외부 조인
    • @OneToMany, @ManyToMany
      • optional = false : 외부 조인
      • optional = true : 외부 조인

8.4 영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때 영속성 전이 기능을 사용 하면 된다.

  • 영속성 전이 : 저장

    @Entity
    public class Parent {
    	
    	@Id @GeneratedValue
    	private Long id;
    
    	@OneToMany(mappedBy = "parent", **cascade = CascadeType.PERSIST**)
    	private List<Child> children = new ArrayList<>();
    	...
    }
    
    @Entity
    public class Child {
    	
    	@Id @GeneratedValue
    	private Long id;
    	
    	@ManyToOne
    	private Parent parent;
    	...
    }
    • CascadeType.PERSIST 를 설정 시 부모객체에 자식객체들을 추가 한 뒤 부모객체만 영속화 해도 자식 객체까지 모두 영속화 되어 저장 된다!
    • 단지 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공 할뿐이다.
  • 영속성 전이 : 삭제

    • CascadeType.REMOVE 설정 시 부모 엔티티를 삭제하면 자식 엔티티까지 모두 삭제 된다.
    • 단 삭제 순서는 외래 키 제약조건을 고려 해 자식 먼저 삭제 후 부모가 삭제 된다.

8.5 고아 객체

  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 고아 객체(ORPHJAN) 제거 라 한다.

  • 사용법 : orphanRemoval = true , 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거 하게되면 자식 엔티티가 자동으로 삭제 되게끔 하는 고아 객체 제거 설정

    Parent parent = em.find(Parent.class, id);
    parent.getChildren().remove(0); // 0번째 인덱스에 있는 자식 엔티티 컬렉션에서 제거
    
    //플러시 일어난 후 SQL
    DELETE FROM CHILD WHERE ID=?
  • 고아 객체 제거는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않은 고아 객체로 보고 삭제 하는 기능

  • 이 기능은 참조하는 곳이 하나일 때만 사용 해야 한다.( 삭제한 엔티티가 다른 곳에서도 참조한다면? 문제가 되므로)

  • orphanRemoval 은 @OneToOne, @OneToMany 에서만 사용할 수 있음

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

  • CascadeType.ALL + orphanRemoval = true 를 동시 이용 시 부모 엔티티를 통해 자식의 생명주기를 관리 할 수 있음.

8.7 정리

  • 지연로딩 시 프록시 기술 사용!
  • 객체 조회 시 연관된 객체를 즉시 로딩하는 방법이 즉시 로딩 이라 하고 연관된 객체를 지연(연관된 객체를 실제 사용할 때까지 지연 시킴) 하여 로딩 하는 방법을 지연 로딩이라 한다.
  • 객체 저장 or 삭제 시 연관된 객체도 함께 처리 하는 것을 영속성 전이 라 한다.
  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제 하려면 고아 객체 제거 기능을 사용 하면 된다.

좋은 웹페이지 즐겨찾기