객체 조회 시 해당 객체가 참조하는 객체의 정보도 한번에 모두 불러와야 하는가?
ex) MEMBER 객체가 TEAM 객체를 참조할 때, MEMBER 조회 시 TEAM도 함께 조회해야 하는가?
😩 이전 코드
Member member = new Member();
member.setMemberName("newMemeber");
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.MemberId = " + findMember.getMemberId());
// findMember.memberId = 1
System.out.println("findMember.memberName = " + findMember.getMemberName());
// findMember.memberName = newMember
쿼리 실행하여 객체 바로 조회
🙂 프록시 이용한 코드
Member member = new Member();
member.setMemberName("newMember");
em.persist(member);
em.flush();
em.clear();
Member findMember = em.getReference(Member.class, member.getId());
// 쿼리가 실행되지 않고 프록시 객체가 조회됨
System.out.println("findMember = " + findMember.getClass());
// findMember = class hello.jpa.Member$HibernateProxy$dsafnAYDS
System.out.println("findMember.id = " + findMember.getId());
// findMemeber.id = 1
// 필드를 조회하기 위한 쿼리가 실행됨
System.out.println("findMember.username = " + findMember.getMemberName());
// findMember.id = newMemeber
getReference() 데이터베이스를 조회할 때 프록시(가짜) 엔티티 객체 조회
메서드 호출 시 쿼리는 실행되지 않고 겉만 있는 빈 객체가 생성된다. 이후 객체의 속성을 호출할 때 쿼리가 실행되고 프록시 객체가 채워진다.
실제 클래스를 상속 받아서 만들어져 실제 클래스와 겉 모양이 같으며(속은 비어있음) 실제 객체의 참조(target)를 보관한다.
사용 시에는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
프록시 객체 호출 시, 실제 객체의 메소드 호출하여 속성 정보를 가져오게 된다.
처음 사용할 때 한 번만 초기화
초기화 시 프록시 객체가 실제 엔티티로 바뀌는 것은 아니나 초기화되면 프록시 객체를 통해 실제 엔티티에 접근 가능
프록시 객체는 원본 엔티티를 상속받음 따라서 타입 체크 시 주의해야 함
* == 비교 대신 instanceOf 사용
// 객체
Member find = em.find(Member.class, member1.getId());
// 프록시 객체
Member reference = em.getReference(Member.class, member2.getId());
System.out.println("== : " + (m1 == m2)); //false
System.out.println("find : " + (find instanceof Member)); //true
System.out.println("reference : " + (reference instanceof Member)); //true
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 1차 캐시 후 em.getReference()를 호출해도 참조하고 있는 실제 엔티티 반환한다.
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때(트랜잭션 범위 밖일때) 프록시 초기화하면 문제 발생한다.
(하이버네이트는 org.hibernate.LazyInitializtionException 예외 터뜨림)
프록시 확인
// 프록시 인스턴스의 초기화 여부 확인
PersistenceUnitUtil.isLoaded(Object entity);
// 프록시 클래스 확인 방법
entity.getClass().getName();
// 프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity);
* 참고로 JPA 표준은 강제 초기화 없음
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
//1
Member member = em.find(Member.class, 1L);
system.System.out.println(findMember.getTeam().getClass());
//2
Team team = member.getTeam();
team.getName();
system.System.out.println(findMember.getTeam().getName());
1) member 객체는 실제 객체로 조회, team 객체는 프록시로 조회 (데이터베이스 조회 x)
select member0_.id as id1_4_0_,
member0_.team_id as team_id11_4_0_,
member0_.name as name9_4_0_,
from Member member0_
where member0_.id=?
실행 결과 : class hello.jpa.Team$HibernateProxy$eafn87ASDF
2) member의 team 조회 후 데이터베이스를 조회하여 team 프록시 객체 초기화
select team0_.id as id1_8_0_,
team0_.name as name6_8_0_
from Team team0_
where team0_.id=?
실행 결과 : teamA
두 객체를 자주 동시에 조회한다면?
@Entity
public class Member extends {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String username;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
Member findMember = em.find(Member.class, member.getId());
System.out.println(findMember.getTeam().getClass());
System.out.println(findMember.getTeam().getName());
쿼리 날릴 때 join 연산으로 team과 member 객체 데이터베이스에서 조회
select member0_.id as id1_4_0_,
member0_.team_id as team_id11_4_0_,
member0_.name as name9_4_0_,team2_.id as id1_8_2_,
team2_.name as name6_8_2_
from Member member0_
left outer join Team team2_
on member0_.team_id=team2_.id
where member0_.id=?
실행 결과: class.hello.jpa.Team / TeamA
참조 관계에 있는 두 객체를 함께 자주 조회해야할 때 사용
예) MEMBER 객체와 TEAM 객체를 함께 자주 사용할 때 즉시 로딩을 이용하면 MEMBER 조회 시 항상 TEAM 도 조회
* 주의 *
실무에서는 가급적 지연 로딩만 사용할 것! 즉시 로딩은 상상하지 못한 쿼리가 발생할 수 있음 (JPQL 에서 N+1 문제를 일으킴)
@ManyToOne, @OneToONe은 기본이 즉시 로딩임으로 LAZY로 설정해 줄 것(@OneToMany, @ManyToMany는 기본이 지연 로딩)
JPQL fetch 조인이나 엔티티 그래프 기능 사용할 것
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때
예) 부모 엔티티를 저장할 때 자식 엔티티도 함께 영속성 컨텍스트에 저장
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST);
CASCADE 종류 | 의미 |
ALL | 모두 적용 |
PERSIST | 영속 |
REMOVE | 삭제 |
MERGE | 병합 |
REFRESH | refresh |
DETACH | 영속성 분리 |
* 주의 *
영속성 전이는 연관관계 매핑과 아무 관련 없음
엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
orphanRemoval = true
Parent parent = em.find(Parent.class, id);
parent.getChildren().remove(0); // 자식 엔티티를 컬렉션에서 제거
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제
DELETE FROM child WHRER childe_id = ?
참조하는 곳이 하나일 때만 사용해야 함(특정 엔티티가 개인을 소유할 때, @OneToOne, @OneToMany만 가능)
개념적으로 부모를 제거하면 자식은 고아가 됨으로 고아 객체 제거 기능을 활성화 하면 부모를 제거할 때 자식도 함께 제거됨
이는 CascadeType.REMOVE처럼 동작
[SPRING] JPA 프로그래밍 기본 | 객체지향 쿼리 언어 (0) | 2021.04.08 |
---|---|
[SPRING] JPA 프로그래밍 기본 | 값 타입 (0) | 2021.04.08 |
[SPRING] JPA 프로그래밍 기본 | 상속 (0) | 2021.03.09 |
[SPRING] JPA 프로그래밍 기본 | 연관관계 (0) | 2021.03.09 |
[SPRING] JPA 프로그래밍 기본 | JPA와 영속성 (3) | 2021.03.08 |
댓글 영역