상세 컨텐츠

본문 제목

[SPRING] JPA 프로그래밍 기본 | 프록시와 지연로딩

JAVA/기본 & 강의복습

by ranlan 2021. 3. 11. 00:22

본문

728x90

객체 조회 시 해당 객체가 참조하는 객체의 정보도 한번에 모두 불러와야 하는가?

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 표준은 강제 초기화 없음

 

 

지연 로딩(Lazy)을 이용하여 프록시로 조회

@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

 

 

즉시 로딩(Eager)을 이용하여 함께 조회 

두 객체를 자주 동시에 조회한다면?

@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 조인이나 엔티티 그래프 기능 사용할 것

 

 

영속성 전이 CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때

예) 부모 엔티티를 저장할 때 자식 엔티티도 함께 영속성 컨텍스트에 저장

@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처럼 동작

 

 

 

728x90

관련글 더보기

댓글 영역