다음 코드의 결과값은? Member refMember = em.getReference(Member.class, member1.getId()); Member findMember = em.find(Member.class, member.getId()); System.out.println(refMember == findMember);
다음 코드의 문제점은? em.persist(member1) em.flush(); em.clear(); Member refMember = em.getReference(Member.class, member1.getId()); em.detach(refMember); refMember.getUsername();
@ManyToOne, @OneToOne의 Default FetchType은?
즉시로딩의 문제점은?
Cascade를 사용할 때 주의점은?
고아 객체를 사용할 때 주의점은?
Cascade + 고아 객체를 함께 사용할 때의 장점은?
em.find() 와 em.getReference()의 실제 동작 차이
em.find()의 경우 호출시 영속성 컨텍스트에 데이터가 없을 경우 바로 DB에 쿼리를 날림
em.getReference()의 경우 호출시 영속성 컨텍스트에 데이터가 없더라도 DB에 쿼리를 날리지 않고 나중에 해당 데이터가 실제로 필요할 때 DB에 쿼리를 날림
getReference는 프록시 기법을 활용해서 조회를 뒤로 미룰 수 있음
프록시
getReference와 같은 함수를 호출하면 실제 클래스를 상속받는 프록시 클래스가 생성됨
실제 클래스와 겉 모습이 같고, 사용자 입장에서는 구분이 되지 않음
실제 동작 순서
프록시 객체의 getName()을 호출
프록시 객체는 PersistenceContext를 통해 엔티티가 초기화 되었는지 확인
이를 위해, 프록시는 영속성 컨텍스트를 통해 해당 엔티티의 데이터가 이미 존재하는지 확인함
데이터가 영속성 컨텍스트에 존재하지 않는 경우 DB를 조회해 데이터를 조회해오고, Member에게 getName()을 호출해 데이터를 조회해옴
데이터가 영속성 컨텍스트에 존재하는 경우 Member에게 바로 getName()을 호출해 데이터를 조회해옴
초기화 이후에 프록시 객체는 바로 Member 객체에게 함수를 호출함
프록시 특징
처음 사용할 때 한 번만 초기화
초기화를 했다고 실제 엔티티를 바뀌는 것은 아니고, 프록시를 통해 실제 엔티티에 접근이 가능한 것
프록시 객체는 원본 엔티티를 상속받은 것이라, 실제로는 다르므로 타입 체크시 == 비교가 아닌 instance of를 사용해야 함
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환함
JPA는 같은 것을 조회한 경우 무조건 == 연산이 true로 나오도록 함
예를 들어 아래 코드는 true를 반환함 Member refMember = em.getReference(Member.class, member1.getId()); Member findMember = em.find(Member.class, member.getId()); System.out.println(refMember == findMember);
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생
getReference 이후 detach, clear, close 처럼 해당 객체를 준영속 상태로 만들어 버리고 나서 초기화하면 문제가 발생
그 이유는, 준영속 상태로 전환된 경우, 엔티티 매니저와의 연결이 끊겨 영속성 컨텍스트를 사용할 수 없는 상태가 되기 때문
반면에, em.find는 항상 새로운 영속 상태의 엔티티를 반환하여 이러한 문제를 회피 가능
예를 들어, 아래 코드는 could not initialize proxy 오류가 발생 em.persist(member1) em.flush(); em.clear(); Member refMember = em.getReference(Member.class, member1.getId()); em.detach(refMember); refMember.getUsername();
이는 이런 단적인 예보다도, 트랜잭션이 끝나면 영속성 컨텍스트가 초기화되면서 문제가 발생할 수 있음
실제로 getReference는 많이 사용이 되지 않지만, 프록시 개념 자체가 추후 지연 로딩 개념을 이해하는데 매우 중요
프록시 강제 초기화: org.hibernate.Hibernate.initialize(entity)
JPA 표준에는 강제 초기화가 없어 member.getName()으로 강제 호출
지연 로딩
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
Member를 조회할 때 Member 안에 있는 Team도 함께 조회를 해야 하는지에 대한 의문
실제로 사용할 때 조회를 하고, 사용을 하지 않을 수도 있으니 미리 조회는 하지 않는게 지연 로딩
@OneToMany, @ManyToMany는 기본이 지연 로딩
즉시 로딩
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
Member와 Team을 자주 함께 사용한다면 항상 부르고 싶을 수 있음.
이 때는 EAGER를 통해 즉시 로딩 활용할 수 있음.
@ManyToOne, @OneToOne은 기본이 즉시 로딩
즉시 로딩의 주의점
실무에서는 즉시로딩은 거의 사용하지 않음
상상하지 못한 많은 쿼리가 나갈 수 있음
즉시 로딩은 JPQL에서 N+1 문제를 일으킴
예를 들어 select m from Member m; 이런 JPQL은 모든 멤버를 조회하고 각 멤버가 또 팀을 조회하므로 엄청나게 많은 조회 쿼리가 나가게 됨
때때로 정말 즉시로딩을 하고 싶다면 JPQL에 fetch를 통해 즉시 로딩 할수있게 하는 방법을 활용하거나 그래프 기능 사용
@ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 LAZY 세팅을 꼭 해줘야 함
영속성 전이: CASCADE
@Entity
public class Parent {
@Id
private Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<>();
...
}
특정 엔티티를 영속 상태로 만들 때 연관된 엔팉이도 함께 영속 상태로 만들고 싶은 경우
예를 들어, 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
위 코드처럼 선언하면, Parent에 여러 children을 입력한 후 parent만 저장해도 children이 모두 저장됨
편리함을 제공하지만 생각지 못하게 전파가 될 수 있으므로 주의해서 사용해야 함
일반적으로 사용을 한다면 일대다 관계에서 사용함
"다"들이 참조하는게 "일" 밖에 없을 경우에만 사용해야함
다른데서 관리하는 곳이 있으면 복잡성이 증가해 예상치 못한 문제가 발생할 수 있기 때문.
CASCADE 종류
ALL: 모두 적용
PERSIST: 영속
REMOVE: 삭제
MERGE: 병합
REFRESH: REFRESH
DETACH: DETACH
고아 객체
@Entity
public class Parent {
@Id
private Long id;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children = new ArrayList<>();
...
}