예) Member를 조회할 때 Team도 함께 조회 해야할까?
private static void printMember(Member member) {
String username = member.getUsername();
System.out.println("username : " + username);
}
private static void printMemberAndTeam(Member member) {
String username = member.getUsername();
System.out.println("username : " + username);
Team team = member.getTeam();
System.out.println("team : " + team.getName());
}
회원만 출력하는 경우 em.find(Member.class, member) 할 경우 member, team 모두 조회하기 때문에 비효율적이다.
em.find() VS em.getReference()
em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회 (query 가 진행)
em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
Member findMember = em.getReference(Member.class, member.getId());
findMember.getName()
프록시 객체는 처음 사용할 때 한 번만 초기화된다.
프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면서 프록시 객체를 통해서 실제 엔티티에 접근 가능한 것이다.
프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야한다.( == 비교 X, 대신 instance of 사용 )
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId()); // proxy type
System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass()));
-> 출력
m1 == m2 : false
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
System.out.println("m1 == m2 : " + (m1 instance of Member));
System.out.println("m1 == m2 : " + (m2 instance of Member));
-> 출력
m1 == m2 : true
m1 == m2 : true
Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1 = " + m1.getClass());
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());
System.out.println("m1 == reference : " + (m1 == reference));
-> 출력
m1 = class hellojpa.Member
reference = class hellojpa.Member
m1 == reference : true
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass()); // Proxy
em.detach(refMember); // detach : 영속성 컨텍스트에서 특정 엔티티를 빼는 것
refMember.getUsername();
-> 출력 결과
Member를 조회할 때 Team도 함께 조회해야 할까?
단순히 member 정보만 사용하는 비지니스 로직 println(menber.getName())
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
... Getter, Setter
// Test
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();
//
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getTeam().getClass());
// proxy 객체이므로 team의 getName을 할 경우 query를 날림
System.out.println("======================================");
//findMember.getTeam() // 이 경우는 proxy값을 가져온다.
findMember.getTeam().getName(); // 실제 team을 사용하는 시점에 초기화(DB조회)
System.out.println("======================================");
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
em.close();
}
emf.close();
}
Member와 Team을 자주 함께 사용한다면?
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
... Getter, Setter
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
이론적
- Member 와 Team은 자주 함께 사용 -> 즉시 로딩
- Member 와 Team은 자주 가끔 사용 -> 지연 로딩
- Order와 Product는 자주 함께 사용 -> 즉시 로딩
- 모든 연관관계에 지연 로딩을 사용해라!
- 실무에서 즉시 로딩을 사용하지 마라!
- JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라! (뒤에서 설명)
- 즉시 로딩은 상상하지 못한 쿼리가 나간다.