실제 클래스를 상속 받아서 만들어진다. ( 타입 체크 시 주의하자. 프록시 객체와 엔티티 객체 간에 타입 비교시 == 비교 instance of를 사용하자 )
사용하는 입장에서 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨 ( 이론상 )
프록시는 실제 객체의 참조를 보관한다.
프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다. (위임)
프록시 객체는 처음 사용할 때 한 번만 초기화된다.
프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니다. 초기화 된 이후에는 프록시 객체를 통해서 실제 엔티티에 접근 가능한 것이다.
em.find 이후 em.getReference : 이미 영속 컨텍스트에 엔티티가 존재하므로 프록시 객체가 아닌 실제 엔티티 가져옴
영속성 컨텍스트에 존재하지 않을 때 em.getReference 두번 : 동일한 프록시 객체 두개가 생성됨 (
영속성 컨텍스트에 존재하지 않을 때 em.getReference 이후 em.find : em.find 로 호출된 객체는 프록시 객체 ( == 연산에서 true가 나오게하기 위함 )
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
프록시 확인
프록시 인스턴스의 초기화 여부 확인
emf.getPersistUnitUtil : emf에서 PersistenceUnitUtil을 받아올 수 있다.
PersistenceUnitUtil.isLoaded(Object entity) : 이를 이용해서 초기화 여부 확인
프록시 클래스 확인 방법
entity.getClass().getName() 출력
프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity);
JPA 표준은 강제 초기화가 없으므로 다른 메소드를 강제 호출하는 식으로 사용
member.getName()
즉시로딩, 지연로딩
지연로딩 : 프록시 객체를 이용하여 해당 객체를 이용하는 시점에 쿼리를 날려 조회해옴
즉시로딩 : 조회할 때 조인해서 바로 연결된 객체를 가져옴
프록시와 즉시로딩 주의
가급적 지연로딩만 사용! ( 특히 실무에서 )
즉시로딩은 예상치 못한 쿼리를 발생시킨다. ( 연관된 객체를 모두 조인해서 가져오기 때문, 성능 문제 생김 )
즉시로딩은 JPQL 사용 시 N+1 문제를 일으킨다.
JPQL 사용할 때는 1개의 조회 요청을 보냈을 때 그와 연관된 N개의 객체가 있다면 이에 각각의 추가 select 문을 날려버림 ( N + 1 )
Member m = em.find(Member.class, member1.getId());
List members = em.createQuery("select m from Member m", Member.class).getResultList();
//SQL : select from Member
//SQL : select from Team where TEAM_ID = XXX
// 쿼리가 연결된 팀의 수만큼 나감
// ( em.find와 달리 JPQL은 그저 쿼리를 만들어 보내기 때문에 TEAM을 또 조회해야함. )
- 이를 해결하기 위해 Lazy로 다 깔아두고 fetch join을 이용
ToOne 으로 매핑된 것은기본이 즉시로딩. LAZY로 선언 필요
ToMany 로 매핑된 것은 기본이 지연로딩
영속성 전이 : CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
예 : 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.persist(child1);
em.persist(child2);
tx.commit();
원래는 위처럼 여러번 자식객체를 persist 해야하는데 이를 부모 객체만 persist하면 자동으로 자식 객체도 persist 시키고 싶을 때 이용한다.
영속성 전이 주의
영속성 전이는 연관관계를 매핑하는 것과는 아무런 관계가 없다.
엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
CASCADE의 종류
ALL : 모두 적용
PERSIST : 영속
REMOVE : 삭제
위 세 개를 주로 사용.
PERSIST 할 때만 CASCADE를 걸려면 PERSIST만 걸어주고 삭제할 때만 걸려면 REMOVE
MERGE : 병합
REFRESH : REFRESH
DETACH : DETACH
언제 사용해야 하는가?
하나의 부모가 자식들을 관리할 때 의미가 큼. ( 게시판과 첨부파일 )
첨부파일의 경로는 한 게시판에서 관리하기 때문
여러 곳에서 관리를 하는 객체라면 사용하면 안된다.
소유자가 하나일 때만 cascade를 사용하자.
즉 단일 엔티티에 종속적일 때만 사용
고아 객체
고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능
orphanRemoval = true
고아객체 - 주의
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
참조하는 곳이 하나일 때 사용해야함.
특정 엔티티가 개인 소유할 때 사용한다.
@OneToOne , OneToMany 만 사용 가능
참고 : 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아객체 제거기능을 활성화하면 부모를 삭제할 때 자식도 함께 삭제가 된다.
CascadeType.REMOVE 처럼 동작한다.
영속성 전이 + 고아 객체, 생명주기
CascadeType.ALL + orphanRemovel=true
스스로 생명주기를 관리하는 엔티티는 em.persist() 로 영속화 하고 em.remove() 로 제거한다.
두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있게 된다.
프록시 기초
em.find()
vsem.getReference()
em.find()
: DB를 통해서 실제 엔티티 객체를 조회em.getReference()
: DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회getReference()
를 실행하는 시점에는 쿼리를 날려 객체를 조회하지 않는다.프록시 특징
==
비교instance of
를 사용하자 )em.find
이후em.getReference
: 이미 영속 컨텍스트에 엔티티가 존재하므로 프록시 객체가 아닌 실제 엔티티 가져옴em.getReference
두번 : 동일한 프록시 객체 두개가 생성됨 (em.getReference
이후em.find
:em.find
로 호출된 객체는 프록시 객체 ( == 연산에서 true가 나오게하기 위함 )프록시 확인
emf.getPersistUnitUtil
: emf에서 PersistenceUnitUtil을 받아올 수 있다.PersistenceUnitUtil.isLoaded(Object entity)
: 이를 이용해서 초기화 여부 확인entity.getClass().getName()
출력org.hibernate.Hibernate.initialize(entity);
member.getName()
즉시로딩, 지연로딩
프록시와 즉시로딩 주의
즉시로딩은 JPQL 사용 시 N+1 문제를 일으킨다.
List members = em.createQuery("select m from Member m", Member.class).getResultList();
//SQL : select from Member //SQL : select from Team where TEAM_ID = XXX // 쿼리가 연결된 팀의 수만큼 나감 // ( em.find와 달리 JPQL은 그저 쿼리를 만들어 보내기 때문에 TEAM을 또 조회해야함. )
ToOne
으로 매핑된 것은기본이 즉시로딩. LAZY로 선언 필요ToMany
로 매핑된 것은 기본이 지연로딩영속성 전이 : CASCADE
영속성 전이 주의
CASCADE의 종류
언제 사용해야 하는가?
고아 객체
orphanRemoval = true
고아객체 - 주의
@OneToOne
,OneToMany
만 사용 가능영속성 전이 + 고아 객체, 생명주기
CascadeType.ALL + orphanRemovel=true
em.persist()
로 영속화 하고em.remove()
로 제거한다.