1. 프록시 객체란
2. 프록시 객체 사용 이유
2-1. 프록시 객체를 이용한 지연 로딩
2-2. 지연 로딩 즉시 로딩 차이
1. 프록시 객체란
프록시 객체는 실제 Entity를 대신하는 가짜 객체이다. 사용법을 보면은 무슨 뜻인지 이해가 갈 것이다.
먼저 java에서 프록시 객체를 생성하는 방법은 이렇다.
entityManager.getReference(타겟엔티티.class, 엔티티의PK)
getReference함수의 리턴값을 확인해 보면 'the found entity instance'라고 적혀있다. 즉 엔티티 객체를 돌려준단 것이다.
그러면 getReference 함수를 통해서도 Entity 객체가 조회되고 find() 함수를 통해서도 Entity가 조회되는데
무슨 차이가 있는걸까?
그 차이는 getClass()를 직접 로깅을 해보는 순간 다르단 걸 알 수 있다.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Book book1 = em.getReference(Book.class, 1L);
Book book2 = em.find(Book.class, 2L);
System.out.println("book1 : "+book1.getClass());
System.out.println("book2 : "+book2.getClass());
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
em.close();
}
emf.close();
실행 결과
그렇다면 외부적으로는 엔티티와 동일하지만 내부는 엄연히 다르단 것을 확인했다 그러면 왜 굳이 저런 객체를 사용을 할까
2. 프록시 객체 사용 이유
아래는 실제 예시이고, Author 객체와 Book Entity가 존재하고 Book 객체 안에서 Author 객체를 참조하고 있다.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
System.out.println("================================================");
Book book1 = em.getReference(Book.class, 1L);
System.out.println("================================================");
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
Book book2 = em.find(Book.class, 2L);
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
em.close();
}
emf.close();
실행 결과
상황은 아래와 같다.
- 먼저와 다르게 getRefence, find를 사용하여 조회만 하였고, 로깅을 하지 않았다.
- em.find를 사용했을 때는 바로 DB를 조회하는 쿼리가 날아간다.
- em.getRerence를 사용한 부분에서는 DB 조회 쿼리가 날아가지 않는다.
그렇다면 조회를 한 뒤 sysout을 통해 화면에 출력을 하면 어떻게 될까.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
System.out.println("================================================");
Book book1 = em.getReference(Book.class, 1L);
System.out.println("book1 :"+book1);
System.out.println("================================================");
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
Book book2 = em.find(Book.class, 2L);
System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
em.close();
}
emf.close();
실행 결과
위와 같이 DB 조회 쿼리가 날아가게 된다.
이러한 프록시 객체를 이용한 것이 JPA에서의 지연로딩이다.
2-1. 프록시 객체를 이용한 지연 로딩
JPA에서 조인을 할 때 지연로딩을 사용하게 되면 연관 객체가 프록시 객체로 조회가 된다. 예시는 아래와 같다.
Book 엔티티
public class Book {
@Id @GeneratedValue/*(strategy = GenerationType.IDENTITY)*/
private Long id;
@Column(name = "book_name")
private String bookName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
public void setId(Long id) {
this.id = id;
}
public void setBookName(String userName) {
this.bookName = bookName;
}
}
Author 엔티티
public class Author {
@Id @GeneratedValue/*(strategy = GenerationType.IDENTITY)*/
private Long id;
private String name;
}
- Book 엔티티가 Author 엔티티와 자식-부모 관계를 가지며 실제 DB에는 위와 같은 데이터가 존재한다.
- Book엔티티의 Author 필드에 @ManyToOne(fetch = FetchType.LAZY)를 붙여 지연 로딩을 사용하게 한다.
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
System.out.println("================================================");
Book book = em.find(Book.class, 1L);
System.out.println("author : "+book.getAuthor().getClass());
System.out.println("================================================");
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
em.close();
}
emf.close();
실행 결과
- Book 엔티티를 조회하여도 연관 객체인 Author에 대한 쿼리는 실제 발생하지 않는다.
- 조회한 book 객체의 Author 객체 class를 로깅하면 프록시 객체로 조회 된것이 확인된다.
위에서 보았듯이 지연로딩은 프록시 객체를 이용하는 것을 확인할 수 있다.
2-2. 지연 로딩 즉시 로딩 차이
JPA에서 즉시 로딩은 Book 엔티티를 조회하면 연관 객체인 Author 객체도 동시에 조회하여 가져오는 것을 말한다.
즉시로딩으로 바꾸어 테스트
public class Book {
@Id @GeneratedValue/*(strategy = GenerationType.IDENTITY)*/
private Long id;
@Column(name = "book_name")
private String bookName;
@ManyToOne/*(fetch = FetchType.LAZY)*/
@JoinColumn(name = "author_id")
private Author author;
public void setId(Long id) {
this.id = id;
}
public void setBookName(String userName) {
this.bookName = bookName;
}
}
- 먼저 지연 로딩 테스트를 위한 사용한 (fetch=FetchType.LAZY)를 주석한다.
- 기본적으로 자식 객체를 조회할 때 부모 객체를 즉시로딩 한다. (@ManyToOne(fetch = FetchType.EAGER) 기본값)
Book 객체 조회 실행 시
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
System.out.println("================================================");
Book book = em.find(Book.class, 1L);
System.out.println("================================================");
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
em.close();
}
emf.close();
}
실행 결과
지연 로딩과 다르게 즉시 로딩은 Book 객체만 조회해도 부모 관계인 Author 객체도 조인하여 가져온다.
위와 같은 이유로 연관 관계를 설정할 때 즉시 로딩을 사용하게 되면 원하지 않는 상황에서도 항상 연관 객체를 같이 조회하게 되고 그에 따라 당연히 필요 없는 자원을 낭비하고 성능을 떨어트릴 수 있다.
이러한 이유로 특수한 경우를 제외하면 즉시 로딩 사용을 피하는 것이 좋다.
참고 자료
'JPA > JPA 기초' 카테고리의 다른 글
JPA 정리(3) - 연관관계, 양방향 매핑 (0) | 2023.07.17 |
---|---|
JPA 정리(2) - 스키마 생성 및 제약 조건 (0) | 2023.07.17 |
JPA 정리(1) - 영속성 컨텍스트 (0) | 2023.07.17 |