[JPA] 기본편 복습 정리 (3)

bin1225·2023년 2월 11일
0

JPA

목록 보기
10/12
post-thumbnail

프록시

프록시 : 가짜 객체, 실제 클래스를 상속받아서 만들어지므로 겉 모양이 같다.

그림처럼 실제 엔티티의 참조값을 보관한다. 그리고 이 참조값을 이용하여 실제 엔티티에서 값을 가져오거나 메소드를 호출한다.

em.find() : 실제 엔티티 조회
em.getReference : 프록시 조회

특징

  • 프록시 객체는 처음 사용할 때 한 번 초기화 된다.
    처음에는 참조값이 비어있으므로, 참조값에 해당하는 객체를 DB에서 찾아와 이용한다.

  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)

  • em.getReference()를 사용해도 영속성 컨텍스트(1차캐시)를 먼저 조회하므로, 실제 엔티티를 반환할 수 있다.

  • 준영속 상태에서 프록시 초기화시 문제가 발생한다.
    -> 영속상태일 때만 초기화 가능

프록시 기능

- 프록시 초기화 여부 확인 : PersistenceUnitUtil.isLoaded(Object entity)
- 클래스 확인 : entity.getClass().getName()

이 프록시를 이용해 지연로딩을 활용할 수 있게 된다.

즉시 로딩과 지연 로딩

 @Entity
 public class Member {
 ...
 
	 @ManyToOne(fetch = FetchType.LAZY) //지연로딩
     @ManyToOne(fetch = FetchType.EAGER) //즉시로딩
	 @JoinColumn(name = "TEAM_ID")
	 private Team team;
 .. 
 }
  • 지연로딩 (fetch = FetchType.LAZY)

    FetchType.LAZY 로 설정하면, Member 객체를 로딩하는 과정에서 Team은 프록시 엔티티로 가져온다.
    실제로 Team을 사용하는 시점에서 초기화 한다.

  • 즉시로딩 (fetch = FetchType.EAGER)
    FetchType.EAGER로 설정하면, Member객체를 조회하면 Team도 함께 조회한다. -> 실제 엔티티를 로딩함

JPA 구현체는 가능하면 조인을 이용해서 한 번에 조회한다.

주의점

  • 가급적 지연 로딩만 사용(특히 실무에서)

  • 즉시로딩은 적용하면 예상하지 못한 SQL이 발생한다.

  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
    예를 들면, member를 N개 조회했는데, 거기에 Team이 EAGER로 설정돼있으면, 처음 조회쿼리 1개 + N번의 Team 조회 쿼리가 발생한다.

@ManyToOne, @OneToOne은 기본이 즉시 로딩이므로
꼭 LAZY로 설정해주자


영속성 전이: CASCADE

@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)

말 그대로 영속성을 전이시킨다.
특정 엔티티를 영속시킬 때, 연관된 엔티티도 함께 영속상태로 만들고싶다면 사용한다.

  • 종류
    • ALL: 모두 적용
    • PERSIST: 영속
    • REMOVE: 삭제
    • MERGE: 병합
    • REFRESH: REFRESH
    • DETACH: DETACH

고아 객체

@OneToOne(orphanRemoval = true) 
@OneToMany(orphanRemoval = true)

부모 객체와 연관관계가 끊어진 자식 객체를 자동으로 삭제한다.
ex) Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거

부모 객체를 제거하더라고 자동으로 삭제된다.

참조하는 곳이 하나일 때, 즉 특정 엔티티가 개인 소유할 때 사용해야한다.

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

CascadeType.ALL + orphanRemovel=true 을 활용하면 부모 엔티티를 통해 자식의 생명주기를 관리할 수 있다.


값 타입

기본 값 타입

• 자바 기본 타입(int, double)
• 래퍼 클래스(Integer, Long)
• String
기본 값 타입은 생명주기를 소속된 엔티티에 의존한다.

자바의 기본 타입은 절대 공유해서 사용하면 안된다.
-> Side effect 발생

임베디드 타입(복합 값 타입)


이런식으로 새로운 값 타입을 정의해서 사용하는 것이다.

사용법

@Embeddable: 값 타입을 정의하는 곳에 표시
@Embedded: 값 타입을 사용하는 곳에 표시
• 기본 생성자 필수

정의하는 곳이나 사용하는 곳 둘중 하나에만 어노테이션을 붙여도 된다.

장점

  • 재사용 가능
  • 응집도가 높아지고 해당 클래스에 해당 값 타입만 사용하는 의미있는 메소드를 정의할 수 있다.

임베디드 타입은 엔티티의 값일 뿐이다. 따라서 실제로 저장되는 테이블은 임베디드 타입을 사용하는 엔티티의 테이블과 같다.

잘 설계한 ORM 애플리케이션은 매핑한 테이블 수보다 클래스의 수가 더 많다고 한다.

임베디드 타입 vs @MappedSupperclass
https://velog.io/@rudwnd33/JPA-MappedSuperclass-vs-Embedded-Type

@AttributeOverrides, @AttributeOverride

한 엔티티에서 같은 값 타입을 사용하는 경우 위의 어노테이션을 사용해서 컬럼명을 재정의 할 수 있다.

값 타입과 불변 객체

자바의 기본타입은 항상 값을 복사한다.
ex)

int a = 3;
int b = a; //값을 복사
b = 4; //a=3, b=4;

하지만 객체 타입은 참조값을 직접 대입하는 것을 막을 방법이 없고, 이렇게 참조값을 공유해서 사용할 경우 Side effect를 피할 수 없다.

ex)

Address a = new Address(Old); 
Address b = a; //객체 타입은 참조를 전달
b. setCity(New) // a.getCity -> New

불변객체

변하지 않는 객체 (immutable object)
객체를 수정이 불가능하도록 만들어서 부작용을 원천 차단한다.

  • 생성자로 값을 설정하고 setter를 만들지 않는다.

값 타입을 비교하기 위해서는 equals() 메소드를 적절하게 재정의 해줘야 한다.

값 타입 컬렉션

  • 타입을 하나 이상 저장할 때 사용한다.
  • @ElementCollection, @CollectionTable 사용
    값 타입을 저장하기 위한 테이블을 생성한다.

값 타입 컬렉션 제약사항

값 타입은 엔티티와 다르게 식별자 개념이 없다. 따라서 값이 변경되면 추적이 어렵고, 변경이 발생하면 관련 데이터를 모두 삭제하고 다시 저장하는 등 비효율적으로 작동한다.

또 모든 컬럼을 묶어 기본키를 구성하므로, null 값이나 중복저장이 불가능하다.

차라리 값 타입 컬렉션 대신 일대다 관계의 엔티티를 만드는 것을 고려한다.
영속성 전이 + 고아객체를 활용하여 값타입처럼 작동하게 할 수 있다.

값 타입은 정말 값 타입이라고 판단될 때만 사용한다. 식별자가 필요하고 지속해서 값을 추적, 변경해야한다면 엔티티를 사용해야 한다.

0개의 댓글

관련 채용 정보