Spring/JPA

6. 다양한 연관 관계 매핑_7.고급매핑_8. 프록시와 연관관계

whyWhale 2021. 1. 27.

# 이전 학습 요약 내용

  • 다중성 (일대일인지 일대다 관계인지 다중성 고려)
  • 단방향,양방향 (한쪽만 참조,양쪽에서 참조 (mappedBy 선언된 클래스는 read 만 가능)
  • 연관관계 주인 (1:N (N쪽이 주인이다.즉 외래키 관리자 이다.))
  • 객체와 테이블의 차이
  • 객체 == 참조를 통해(힌쪽에서만 참조를 하는지(단방향인지) 또는 양쪽에서 참조가 가능한 (양방향인지)
  • 테이블 ==  키로 조인을 통해 양방향 쿼리가 가능

 

다양한 연관 관계 매핑

핵심요약

 

  • 다대일 양방향 매핑을 되도록이면 사용하기를 권장.(M:N,1:N보다는이 방법 선호)
    •   이유 : 관리해야 하는 외래키가 본인 테이블에 있다. 사용하기 용이하다.
  • 일대일 관계는 양쪽이 서로 하나의 관계만 가진다.
    • 주테이블 이나 대상 테이블이 외래키를 가질 수 있다.(둘중 하나 선택)
      • 주테이블 외래키 -> JPA에서 지원 (객체지향적) 
      • 대상테이블 외래키 -> JAP2.0 이하 지원을 하지 않는다. 양방향으로 선언을 해야 한다.
      • ** 프록시 사용시 외래키 직접 관리하지 않는 (대상 테이블) 일대일 관계는 지연 로딩을 설정해도 즉시 로딩된다. 프록시의 한계로 인하여 발생하는 문제이므로 bytecode instrumentation 을 사용하면 해결할 수 있다. **
  • 다대다 관계 (직접 두 테이블간 관계를 맺을 수 없고 중간 테이블을 생성해야 한다.)
    • 실질적으로 실무에서 사용하기에 한계가 있다. 회원과 상품 중간 과정에는 회원이 구매한 상품정보 만 가질 수 있는 한계가 있다. 추가적으로 개선해야 될 부분이 단순히 상품정보에서만 끝나는 것이 아닌 몇개를 주문하였고 주문한 날짜가 언제인지 등이 더 필요하므로 중간테이블에 이러한 정보들을 추가하여 생성한다. 이렇게 되면 ManyToMany( 두테이블간의 연관관계)를 사용할 수없다. 다대일 일대다 로 풀어내어 매핑을 해야한다.
    • 중간테이블 전략
      • 복합 기본키 사용
        • 외래키를 기본키로 동시에 사용함으로써 한번에 매핑하는 방식.
        • @Idclass 사용하고 별도의 식별자 클래스 생성.
        • Serializable 구현
        • equals 와 HashCode 구현( Lombok -> @EqualsAndHashCode)
        • 식별자 class 는 public
        • @Idclass 외로 @embededId 사용 방법도 있다.
        • 복합키는 항상 식별자 클래스를 생성해야 한다. (방법이 복잡하다)
        • ORM Mapping 에서 처리해할 일이 많아진다.
        • 간단히 다대다 관계를 구성하는 방법이 존재(새로운 기본 키 사용(대리키 사용).)
      • 대리키 사용(비 식별 관게) 
        •  
  • 새로운 키 사용(대리키 사용)
    • 식별관계 클래스 생성 .. 등 ORM Mapping 에서 처리하는 일이 단순해진다.

고급매핑

객체의 상속관계 <---------> 테이블의 상속관계(슈퍼타입과 서브타입)

 

전략 3가지

  • 각각 테이블 변환==조인전략.@Inheritance(strategy=InheritanceType.JOINED)
    • 부모 테이블의 기본키를 받아 pk+fk 사용하는 전략
    • 객체는 타입으로 구분해야 하지만 테이블은 타입이라는 개념이 존재하지 않는다 . 따라서 타입을 구분하는 컬럼을 추가해야 한다. @DiscriminatorCOlumn(name="DTYPE") 사용.
    • 테이블이 정규화 되고 참조 무결성 제약조건도 활용가능하며 저장공간이 효율적이지만. 빈번한 조인이 발생하여 성능저하의 우려가 있으며 쿼리 또한 복잡해지며 데이터를 등록시 부모테이블, 자식테이블(삽입 하는 대상) 2번의 쿼리가 실행된다.

 

  •  통합 테이블 변환== 단일 테이블 전략. Single_Table
    • 조인전략과 동일하게 자식테이블을 타입으로 구분해야 한다.
    • nullable=true 가 생긴다.
    • 조인이 없어 조회 성능이 빠르고 조호 쿼리가 복잡하지 않지만 자식 엔티티가 매핑한 컬럼 모두는 널값을 허용하고 테이블의 크기가 커질때에는 조회 성능이 떨어질 수 있는 우려가 있다.

 

  • 서브타입 테이블로 변환
    • 부모를 클래스가 아닌 추상 클래스를 만들어 상속하는 개념이다.
    • 서브타입을 구분해서 처리할 때 효과적이고 notnull 전략을 사용가능하다 하지만 여러 자식테이블을 함께 조회시 성능이 느리고 자식 테이블을 통합하여 쿼리를 작성하는 것이 어렵다. (가장 추천하지 않는 전략)

 

지금까지는 다른 부모 클래스 자식 클래스를 테이블로 매핑을 하였지만 테이블로 매핑하지 않고 부모 클래스만 상속받는 클래스에게만 정보만을 제공하는 @MappedSuperclass가 있다. (부모는 테이블로 생성되지 않고 정보만 제공.

 

 

  • 식별관계 _ 비식별관계
    • 식별관계는 외래키이자 테이블의 기본키로 사용하는 전략
      • JPA에서는 식별관계 사용시 식별자 클래스를 따로 생성해야 줘야 하고 구분하기 위한 equals , hashcode를 사용해야 한다.
      • @Idclass(데이터베이스 가까움.) , @embeded(객체 지향에 가깝다.) 두가지 방법중 한가지를 선택해야 하고
    • 비식별관계는 외래키는 외래키 기본키는 따로 생성하여 사용하는 전략.(주로사용)
      • 필수적 : 외래키에 Null을 허용하지 않는다. InnerJoin 권장
      • 선택적 : 외래키에 Null을 허용한다.  OuterJoin 권장
  • 식별관계는 테이블 구조가 유현하지 못하고 복잡하여 선호하지 않는 방식이다. 하지만 식별관계는 기본키 인덱스를 활용하기도 좋고 특정상황에 조인 없이 하위 테이블의 검색만으로도 접근이 용이하다는 장점이 있다.

 

  • 조인테이블
    • 테이블을 하나 추가해야되는 부담이 있지만
    • 주로 다대다 관계를 일대다 ,다대일 관계로 풀어내기 위해 사용한다. 그렇지만 일대일 다대일 관계에서 사용을 하긴한다.

 

 

 

 


프록시와 연관관계

객체는 객체 그래프로 연관된 객체들을 탐색한다. 하지만 객체가 DB에 저장되어 있어 마음껏 탐색하기 힘들다. 그래서 JPA 구현체들은 이 문제를 해결하기 위해 프록시라는 기술을 사용한다. 프록시를 사용하면 연관 객체를 처음부터 DB에서 꺼내오는 것이 아닌 실제로 사용하는 시점!에 사용한다. 하지만 자주 함께 사용하는(조인)하는 것이 효과적이다. JPA에서는 지연로딩과 즉시로딩 두 방법을 제공한다.

 

즉 프록시는 실제 사용되어져야 할 시점에만 일을 하는 기술이라고 생각하면 될 것 같다.

예를 들자면 회원과 멤버 관계(N:1)에서(멤버는 외래키 관리자 팀은 읽기 속성 부여받지않는 단방향(팀은 멤버를 참조 할수 없음.)) 어떤 메소드가 회원도 조회하고 팀도 조회하는 메소드 1이 있고 팀만 조회하는 메소드 2가 있다고 가정해보고 메소드1에서도 팀,멤버를 둘다 조회하는 것은 맞다 하지만 메소드 2에서 팀만 조회하면 되는데 굳이 회원도 조회를 해야하는가? 이 메소드 2가 사용될때 프록시는 조회를 지연하는 방법을 제공한다 이것을 지연 로딩이라고 한다.

지연로딩을 사용하기 위해 가짜 객체가 필요한데 이것을 프록시 객체라고 한다.

 

 

  • 프록시
    • 데이터베이스 접근을 위임한 프록시 객체를 반환한다.
    • 실제 클래스(객체)를 상속받아서 만들어 지므로 상속 관계와 유사하다.(실제 객체를 참조만 하고 있다)
    • client는 실제 객체인지 프록시 객체인지 알 수 없다.
    • 프록시 객체 초기화는 실제적으로 
    • 과정
      • client -> 메소드 호출 요청) -> 영속성 컨텍스트(초기화 요청) -> DB조회 -> 실제 객체 생성 -> 실제 객체 메소드를 참조함(프록시에서)
    • 프록시 객체는 처음 사용시 한번만 초기화 진행이 되는 것이 아니고 실제 엔티티로 바뀌는 것이 아니다. 실제 엔티티에 접근만 하는 것이다. 여기서의 접근은 원본 객체에 상속을 받는다. 그래서 타입체크시 주의가 필요하다.
    • 만약 영속컨텍스트에 실제 엔티티가 존재하면 프록시가 아닌 실제 엔티티를 반환한다.
    • 초기화 과정은 영속성 컨텍스트의 도움을 받아야 가능하다. 준영속 시에는 문제가 발생.
    • 프록시 확인도 가능하다.
  • 즉시로딩과 지연로딩
    • 즉시로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다. fetch= FetchType.EAGER
      • 연관관계가 있으면 무조건 같이 조회가 진행된다.
      • 즉시로딩시 조인쿼리를 사용하는 최적화가 필요하다.
        • 왜냐하면 여러 객체를 조회하는 것 보다는 한 조인테이블만 조회하는 것이 훨씬 성능에 이점이 있기 때문이다.
        • NULL제약조건을 잘 봐야한다. NULL이 허용될 경우 OUTER JOIN을 진행해야 한다. 안그러면 어떠한 레코드도 나오지 않는다.
    • 지연로딩 : 연관된 엔티티를 실제 사용할 때 조회한다. fetch = FetchType=LAZY
      • 연관관계가 있어도 실질적인 사용이 없는 한 조회 쿼리를 함부로 날리지 않은 것에 있어 성능상 장점이 있다.
      • 지연로딩의 활용.
        • 연관관계가 별로 없는 테이블은 조회하지 않게 LAZY를 설정한다.
    • JPA 기본 패치 전략.
      • 연관된 엔티티가 하나면 즉시 로딩.
      • 컬렉션이면 지연 로딩 사용.
      • 추천하는 방법: 모든 것을 지연로딩으로 한 후 나중에 즉시로딩할 관계를 구분하고 최적화하는 조인전략을 사용한다.
      • EAGER 타입 주의
        • 컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.
        • 컬렉션 즉시 로딩은 항상 외부 조인을 활용한다.
        • ManyToOne,OneToOne -- optional (널을 허용할것인가 이다.)
          • false  : 내부조인(null이 없으므로)
          • true  : 외부조인
        •  OnetoMany,ManyToMany--optional
          • false : 외부조인 
          • false : 외부조인
  • 영속성 전이 CASCADE
    • 부모 객체를 올리고 그와 연관된 자식들의 객체도 올려야 하는 번거로움을 줄일수 있다.
    • 부모와 연관된 객체들도 영속성 컨텍스트에 올라간다.
    • cascade=CasCadeType.PERSIST - 한번에 영속성에 올리기
    • cascade=CasCadeType.REMOVE - 부모만 지우면 연관된 것들 모조리 지운다.
  • 고아 객체
    • JPA는 부모 엔티티와 연결이 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이것을 고아 객체 제거라고 한다. (ORPHAN) 
    • 즉 부모 엔티티에서 자식 연결만 잘라주면 알아서 삭제해준다.

==> 양속성전이와 고아객체의 생명주기를 true로 한다면 엔티티 스스로 생명주기를 관리한다는 뜻으로 두 옵션을 활성화 하면 부모 엔티티를 통해서 각자의 생명주기를 관리 받아 편리하다.

 

 

 

댓글