프록시 : 가짜 객체, 실제 클래스를 상속받아서 만들어지므로 겉 모양이 같다.
그림처럼 실제 엔티티의 참조값을 보관한다. 그리고 이 참조값을 이용하여 실제 엔티티에서 값을 가져오거나 메소드를 호출한다.
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
을 사용하는 시점에서 초기화 한다.
FetchType.EAGER
로 설정하면, Member
객체를 조회하면 Team
도 함께 조회한다. -> 실제 엔티티를 로딩함JPA 구현체는 가능하면 조인을 이용해서 한 번에 조회한다.
가급적 지연 로딩만 사용(특히 실무에서)
즉시로딩은 적용하면 예상하지 못한 SQL이 발생한다.
즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
예를 들면, member를 N개 조회했는데, 거기에 Team이 EAGER로 설정돼있으면, 처음 조회쿼리 1개 + N번의 Team 조회 쿼리가 발생한다.
@ManyToOne, @OneToOne은 기본이 즉시 로딩이므로
꼭 LAZY로 설정해주자
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
말 그대로 영속성을 전이시킨다.
특정 엔티티를 영속시킬 때, 연관된 엔티티도 함께 영속상태로 만들고싶다면 사용한다.
@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
한 엔티티에서 같은 값 타입을 사용하는 경우 위의 어노테이션을 사용해서 컬럼명을 재정의 할 수 있다.
자바의 기본타입은 항상 값을 복사한다.
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)
객체를 수정이 불가능하도록 만들어서 부작용을 원천 차단한다.
값 타입을 비교하기 위해서는 equals() 메소드를 적절하게 재정의 해줘야 한다.
@ElementCollection
, @CollectionTable
사용값 타입은 엔티티와 다르게 식별자 개념이 없다. 따라서 값이 변경되면 추적이 어렵고, 변경이 발생하면 관련 데이터를 모두 삭제하고 다시 저장하는 등 비효율적으로 작동한다.
또 모든 컬럼을 묶어 기본키를 구성하므로, null 값이나 중복저장이 불가능하다.
차라리 값 타입 컬렉션 대신 일대다 관계의 엔티티를 만드는 것을 고려한다.
영속성 전이 + 고아객체를 활용하여 값타입처럼 작동하게 할 수 있다.
값 타입은 정말 값 타입이라고 판단될 때만 사용한다. 식별자가 필요하고 지속해서 값을 추적, 변경해야한다면 엔티티를 사용해야 한다.