em.find()
: 데이터베이스를 통해서 실제 엔티티 객체 조회em.getReference()
: 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회Member member = em.getReference(Member.class, 1L);
System.out.println("member = " + member.getClass()); // HibernateProxy 객체
==
비교 실패, 대신 instance of
사용)m1.getClass() == m2.getClass() // false
m1 instanceof Member // true
m2 instanceof Member // true
em.getReference()
를 호출해도 실제 엔티티 반환Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1 = "+ m1.getClass()); // Member
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " reference.getClass()); // Member
m1 == reference // true
getReference()
로 프록시 객체를 가지고 있으면 실제로 find()를 했을 때도 프록시 객체를 반환Member refMember = em.getReference(Member.class, member.getId());
System.out.println("refMember = "+ refMember.getClass()); // Proxy
em.detach(refMember); // em.clear
refMember.getUsername(); // org.hibernate.LazyInitializationException
Member member = em.getRefernce(Member.class, "id1"); // (1)
member.getName(); // (2)
프록시 인스턴스의 초기화 여부 확인
ersistenceUnitUtil.isLoaded(Object entity)
프록시 클래스 확인 방법
ntity.getClass().getName() 출력(..javasist.. or ibernateProxy…)
프록시 강제 초기화
rg.hibernate.Hibernate.initialize(entity);
참고 : JPA 표준은 강제 초기화 없음
강제 호출 : ember.getName()
지연 로딩 LAZY
을 사용해서 프록시로 조회fetch = FetchType.LAZY
@Entity
public class Member{
...
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩 사용
@JoinColumn(name="TEAM_ID")
private Team team;
...
}
...
Member m = em.find(Member.class, member1.getId()); // Member 객체 반환
System.out.println("m = "+ m.getTeam().getClass()); // Team$HibernateProxy 객체 반환
m.getTeam().getName() // team을 실제로 사용하는 시점에서 db 조회 엔티티 반환
...
즉시 로딩 EAGER
를 사용해서 함께 조회fetch = FetchType.EAGER
실무에서는 가급적 지연 로딩만 사용
즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
즉시 로딩은 JPQL에서 N+1 문제
를 일으킨다.
@Entity
public class Member{
...
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩 사용
@JoinColumn(name="TEAM_ID")
private Team team;
...
}
...
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
// SQL : select * from Member
// SQL : select * from Team where TEAM_ID = xxx
...
위 JPQL을 그대로 쿼리로 번역하게 되면 Member를 가져오기 위한 쿼리 수행 이후 바로 Member 내부의 Team을 가져오기 위한 쿼리를 다시 수행하게 된다 → N+1 (1개의 쿼리를 날리면 +N 개의 쿼리가 추가 수행된다)
@ManyToOne
, @OneToOne
은 기본이 즉시 로딩
으로 되어 있다. → 직접 전부 LAZY로 설정@OneToMany
, @ManyToMany
는 기본이 지연 로딩
전부 지연 로딩으로 설정한 후, 가져와야 하는 엔티티에 한해서 fetch join
을 사용해서 가져온다.
List<Member> members = em.createQuery("select m from Member m fetch join m.team", Member.class).getResultList();
fetch join
이나, 엔티티 그래프
기능을 사용해라.특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용
@Entity
public class Parent{
...
@OneToMany(mappedBy = "parent", cascade=CascadeType.ALL) // 영속성 전이 속성(CASCADE)사용
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
...
}
@Entity
public class Child{
...
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
...
}
...
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
ALL
: 모두 적용PERSIST
: 영속REMOVE
: 삭제MERGE
: 병합REFRESH
: REFRESHDETACH
: DETACH하지만, 해당 엔티티(Child)가 특정 엔티티(Parent)에 종속되지 않고 여러 군데서 사용된다면 사용하지 않는 게 좋다.
고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
orphanRemoval = true
@Entity
public class Parent{
...
@OneToMany(mappedBy = "parent", cascade=CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
...
}
@OneToOne
, @OneToMany
만 가능CascadeType.ALL + orphanRemoval=true
부모 엔티티를 통해서 자식의 생명주기 관리가 가능
하다.