em.find() vs em.getReference()를 비교해보면,
em.find() : 데이터베이스를 통해 실제 엔티티 객체를 조회, sql 쿼리문이 나간다.
em.getReference() : 엔티티 객체의 필드를 조회하려고 할 때 그 때 데이터베이스에 대한 sql쿼리문이 실행, 데이터베이스 조회를 미룬다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setUserName("hello");
em.persist(member);
em.flush();
em.clear();
// Member findMember = em.find(Member.class, member.getId());
// System.out.println("findMember.id = "+findMember.getId());
Member findMember2 = em.getReference(Member.class, member.getId());//1
System.out.println("findMember = "+ findMember2.getClass()); //0
System.out.println("findMember.id = "+findMember2.getId());//2
System.out.println("findMember.id = "+findMember2.getUserName()); //3
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
1, 2번 -- SELECT 쿼리문이 실행x
3번 -- SELECT 쿼리문이 실행o
0번 -- findMember = class jpabook.Member$HibernateProxy$z10qFr6p
로 출력된다.
= 프록시가 붙은 객체임을 알 수 있다.
em.getReference()
:
instance of
사용(== 체크X)em.getReference
하는 경우, 영속성 컨텍스트 실제 엔티티객체를 상속받음(이 경우 프록시 객체가 X)(반대도 성립!)Member m1 = em.find(Member.class, member1.getId());
Member m1Reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = :"+m1Reference.getClass());
System.out.println("m1 == m1Reference: "+(m1 == m1Reference));
[출력결과]
reference = :class jpabook.Member
m1 == m1Reference: true
(== 비교에도 true로 출력됨)
=> JPA는 한 트랜잭션 안에서 같은 영속성 컨텍스트에서 가져온 객체로 == 비교를 한다면 true로 보장
em.getReference
로 프록시 객체를 반환 받은 후 -> em.find
하는 경우에도 같은 프록시 객체를 반환받게 된다.
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태
일 때 프록시를 초기화하면 문제 발생
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member1 = new Member();
member1.setUserName("hello");
em.persist(member1);
em.flush();
em.clear();
Member m1Reference = em.getReference(Member.class, member1.getId());
System.out.println("m1 = :"+m1Reference.getClass());//Proxy
em.detach(m1Reference);//영속성 컨텍스트에서 관리 안해!
//em.close();도 마찬가지로 에러를 발생시킨다.
//em.clear();도 마찬가지
m1Reference.getUserName();//SELECT 쿼리 발생(영속성 컨텍스트의 도움을 받아서)
tx.commit();
}catch(Exception e){
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
emf.getPersistenceUnitUtil().isLoaded(프록시 객체)
or Hibernate.initialize(프록시 객체)
Member member1 = new Member();
member1.setUserName("hello");
em.persist(member1);
em.flush();
em.clear();
Member m1Reference = em.getReference(Member.class, member1.getId());
System.out.println("m1 = :"+m1Reference.getClass());//Proxy
m1Reference.getUserName(); //강제 호출
System.out.println("isLoaded = "+ emf.getPersistenceUnitUtil().isLoaded(m1Reference));
tx.commit();
1) 지연로딩의 경우
@Id
@GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY)//지연로딩..멤버클래스만 디비에서 조회
@JoinColumn(name = "TEAM_ID") //읽기전용
private Team team;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList<>();
[순서]
1. 로딩 -> MEMBER 객체 영속성 컨텍스트에 저장
2. 지연로딩 LAZY -> TEAM 엔티티 프록시 객체 생성
[테스트 결과]
//프록시 = FetchType.LAZY
Member m = em.find(Member.class, member1.getId());//member만 SELECT
System.out.println("team = "+m.getTeam().getClass());//Team 정보는 프록시로 가져온 것
team = class jpabook.Team$HibernateProxy$YpIAexe7
출력됨을 확인-> Member만 많이 사용하는 경우 LAZY 지연로딩을 사용
2) 즉시로딩의 경우
@Id
@GeneratedValue //생략하면 AUTO
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne(fetch = FetchType.EAGER)//즉시로딩..멤버클래스와 팀 클래스 조인으로 함께 디비에서 조회
@JoinColumn(name = "TEAM_ID") //읽기전용
private Team team;
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList<>();
[순서]
1. 로딩 -> MEMBER 객체 영속성 컨텍스트에 저장
2. 지연로딩 LAZY -> TEAM 실제 엔티티 객체 생성
[테스트 결과]
//프록시 = FetchType.LAZY
Member m = em.find(Member.class, member1.getId());//member만 SELECT
System.out.println("team = "+m.getTeam().getClass());//Team 정보는 실제 엔티티 클래스가 출력된다.
System.out.println("==================");
System.out.println("team.getName() = "+m.getTeam().getName());
System.out.println("==================");
team = class jpabook.Team
출력됨을 확인-> Member와 TEAM을 함께 많이 사용하는 경우 EAGER 즉시로딩을 사용
LAZY + join fetch
..한번에 Member와 Team을 조인하여 한번에 값들을 가져온다. )//프록시 = FetchType.LAZY
Member m = em.find(Member.class, member1.getId());//member만 SELECT
System.out.println("team = "+m.getTeam().getClass());//Team 정보는 프록시로 가져온 것
List<Member> members = em.createQuery("select m from Member m join fetch m.team", Member.class).getResultList();
Hibernate:
/* select
m
from
Member m
join
fetch m.team */ select
member0_.MEMBER_ID as MEMBER_I1_4_0_,
team1_.TEAM_ID as TEAM_ID1_9_1_,
member0_.createdBy as createdB2_4_0_,
member0_.createdDate as createdD3_4_0_,
member0_.lastModifiedBy as lastModi4_4_0_,
member0_.lastModifiedDate as lastModi5_4_0_,
member0_.TEAM_ID as TEAM_ID7_4_0_,
member0_.username as username6_4_0_,
team1_.createdBy as createdB2_9_1_,
team1_.createdDate as createdD3_9_1_,
team1_.lastModifiedBy as lastModi4_9_1_,
team1_.lastModifiedDate as lastModi5_9_1_,
team1_.name as name6_9_1_
from
Member member0_
inner join
TEAM team1_
on member0_.TEAM_ID=team1_.TEAM_ID
[예]
package jpabook;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){//양방향 연관관계
childList.add(child);
child.setParent(this);
}
}
package jpabook;
import javax.persistence.*;
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
}
ntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);//자동으로 child도 persist로 되었으면 좋겠다..how?
em.persist(child1);
em.persist(child2);
tx.commit();
}catch(Exception e){
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
이 경우 INSERT 문이 총 3개가 출력..PARENT만 PERSIST해도 관련된 child는 모두 PERSIST 하고 싶을 때, cascade를 사용하도록 하자
[변경된 코드]
package jpabook;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){//양방향 연관관계
childList.add(child);
child.setParent(this);
}
}
em.persist(parent);
결과는 INSERT문이 총 3개가 나가며, parent와 관련된 child까지 모두 PERSIST된다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
[Test]
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);//자동으로 child도 persist로 되었으면 좋겠다..how?
em.flush();
em.clear();
Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0);
/* delete jpabook.Child */ delete
from
Child
where
id=?
이러한 쿼리문이 나간다.
orphanRemoval = true
= CascadeType.REMOVE
처럼 동작(parent 삭제되도 child 모두 함께 삭제됨을 확인함 = CascadeType.ALL
에서도 마찬가지)CascadeType.ALL + orphanRemoval=true
을 모두 사용하는 경우