Java 공부/자바 ORM 표준 JPA 프로그래밍

8장 프록시와 연관관계 관리

모항 2023. 7. 17. 20:03

주요 내용 요약

프록시: 프록시란 무엇인가? 어떻게 작동하고 사용하는가?

즉시 로딩과 지연 로딩: 즉시 로딩과 지연 로딩은 무엇인가? 각각 어느 상황에 사용해야 알맞은가? 이를 언제 판단하는 것이 좋은가? 컬렉션 래퍼는 무엇인가?

영속성 전이: 영속성 전이란 무엇인가? 종류는 어떠한가?

고아 객체: 고아 객체란 무엇인가? JPA가 고아 객체를 자동으로 삭제하게 하려면 어떻게 해야 하는가?

 

 

프록시

한 마디로 정리하자면, 실제 클래스를 상속 받아 만들어진 가짜 객체이다.

 

여러 데이터 A와 B가 서로 연관관계를 가질 때, A를 조회할 때마다 이에 연결된 B 데이터까지 무조건 함께 조회할 필요가 있을까? 그렇게 하면 어플리케이션의 효율이 떨어질 위험이 있다.

이럴 때, 연관된 객체의 조회를 미루어두기 위해 사용하는 임시 객체가 바로 프록시이다.

 

예를 들면 이렇다.

Member '퍼비'가 Team '백엔드'에 속해있다. 둘은 외래 키로 연결되어 연관관계를 가진다.

클라이언트가 '퍼비'의 이름을 조회하는 요청을 보냈을 때, 프로그램은 Member '퍼비'의 데이터만 가져오면 된다. 어떤 팀에 속해있는지는 당장 조회하지 않아도 된다.

이런 상황에서 프록시가 사용된다.

Member '퍼비'의 이름을 조회하는 요청이 들어왔을 때, Team '백엔드'까지는 데이터베이스에서 조회하지 않도록 프록시 즉 가짜 객체를 만들어둔다. 이를 통해 Team 정보의 조회를 지연시킨다.

 

이때, 프록시가 데이터베이스를 조회하여 참조 값을 채우는 것을 초기화라고 한다.

 

 

즉시 로딩과 지연 로딩

지연 로딩은, 위에서 설명한 바와 같이, 한 객체를 조회할 때 연결된 다른 객체의 조회를 일단 미루어두는 것을 의미한다.

지연 로딩을 하면 일단 하나의 테이블을 조회하는 쿼리문을 실행한 후, 지연을 해두었다가, 후에 연관 테이블이 조회되어야 할 때 그 테이블을 조회하는 퀴리문을 따로 실행한다.

 

즉시 로딩은 이와 반대이다. A와 B를 즉시 로딩으로 연결해두면 A를 조회하였을 때 B 또한 항상 동시에 함께 조회된다.

즉시 로딩을 하면 연관된 여러 테이블을 JOIN하여 하나의 쿼리로 데이터를 한 번에 가져온다.

 

프록시 객체는 주로, 연관관계가 있는 엔티티를 지연 로딩할 때 사용된다.

 

이를 결정하는 코드는 @ManyToOne 어노테이션의 속성인 fetch이다. 아래와 같이 작성한다.

@ManyToOne(fetch = FetchType.EAGER)	// 즉시 로딩

@ManyToOne(fetch = FetchType.LAZY)	// 지연 로딩

 

각각 어느 상황에 사용해야 알맞은가?

모든 연관관계를 지연 로딩으로 설정하면 무조건 좋은 것일까? 그렇지 않다!

 

어플리케이션의 로직을 살펴, 둘 중 어느 것이 알맞을지를 판단해야 한다.

 

  • 즉시 로딩이 좋은 경우: 어플리케이션의 로직상, A와 B의 데이터가 대부분의 경우에 함께 사용되는 경우
  • 지연 로딩이 좋은 경우: 어플리케이션의 로직상, A와 B의 데이터가 함께 사용되는 경우가 많지 않은 경우
  • 기본 전략: 연관된 엔티티가 하나일 경우 즉시 로딩을 사용한다. 연관된 엔티티가 여러 개, 즉 컬렉션의 형태일 경우 지연 로딩을 사용한다. 그 이유는 이렇다. 연관된 엔티티를 쓸데 없이 로딩하게 된 상황에서, 로딩된 엔티티가 단 하나라면 부담이 비교적 적다. 그러나 로딩된 엔티티가 수만 건의 데이터를 포함하는 컬렉션이라면 문제가 커진다.

 

이를 판단하는 시점은 언제가 좋은가?

즉시 로딩과 지연 로딩 중 어느 것을 사용할지를 개발 과정 중 어느 때에 판단하는 것이 가장 효율적일까?

 

연관관계를 설정할 때마다,

즉 @ManyToOne 어노테이션 하나하나를 새롭게 작성해야 할 때마다 이를 판단하여 결정해야 할까?

 

책에 따르면, 일단 모든 관계를 지연 로딩으로 설정해둔 뒤,

어플리케이션 완성에 가까워진 시점에서 즉시 로딩이 적합한 관계를 찾아내어 변경하는 것이 효율적이라고 한다.

 

컬렉션 래퍼

컬렉션 래퍼란, 엔티티에 포함된 컬렉션(List, Set 등)을 추적 및 관리하기 위해 사용되는 하이버네이트 내장 컬렉션을 말한다.

기본적인 엔티티는 프록시가 지연 로딩을 처리한다. 하지만 컬렉션의 경우에는 컬렉션 래퍼가 지연 로딩을 처리한다.

 

 

영속성 전이란 무엇인가?

한 엔티티의 영속성을 변경할 때 그와 연관관계를 갖는 다른 엔티티도 함께 영속성을 변경하는 것을 의미한다.

쉽게 설명하자면,

특정 데이터를 저장할 때 다른 데이터도 함께 저장하고 싶을 때

특정 데이터를 삭제할 때 다른 데이터도 함께 삭제하고 싶을 때

등의 상황에 사용한다.

 

영속성 전이는 어노테이션의 casecade 속성을 설정함으로써 사용할 수 있다.

 

영속성 전이의 종류

영속성 전이의 방식을 결정하는 cascadeType의 옵션은 아래와 같이 6가지가 있다.

  • ALL : 모두 적용
  • PERSIST : 영속
  • MERGE : 병합
  • REMOVE : 삭제
  • REFRESH : refresh
  • DETACH : detach

예를 들어, 회원 객체의 영속성이 변경될 때 그 회원이 작성한 게시글의 영속성까지 모두 함께 변경하고 싶다면 아래와 같이 코드를 작성하면 된다.

@OneToMany(mappedBy = "writer", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<Post>();

 

회원 데이터가 삭제될 때 게시글 데이터 또한 모두 삭제되기를 원한다면 다음과 같이 작성한다.

@OneToMany(mappedBy = "writer", cascade = CascadeType.REMOVE)
private List<Post> posts = new ArrayList<Post>();

이렇게 하면 em.remove(회원 객체)만 실행하여도 그에 딸린 게시글까지 모두 삭제된다.

만약 casecade = CasecadeType.REMOVE를 적지 않은 상태에서 em.remove(회원 객체)만 실행한다면, 그에 딸린 게시글 객체들은 존재하지 않는 회원 객체를 외래 키로 참조하는 상태가 된다. 이는 데이터베이스에서 외래 키 무결성 예외를 발생시킨다!

 

 

아래와 같이 한 번에 여러 속성을 함께 부여하는 것도 가능하다.

@OneToMany(mappedBy = "writer", cascade = CascadeType.PERSIST, CascadeType.REMOVE)
private List<Post> posts = new ArrayList<Post>();

 

 

고아 객체

고아 객체란, 부모 엔티티와 관계가 끊어진 자식 객체를 의미한다.

예를 들어, 회원이 게시글을 지워 게시글 데이터가 삭제되었는데, 그 게시글에 달려있던 댓글의 데이터가 삭제되지 않고 남아있다면, 댓글 데이터는 고아 객체인 것이다.

 

JPA는 이러한 고아 객체를 자동으로 삭제하는 기능을 제공한다.

 

예를 들면 다음과 같다.

@OneToMany(mappedBy = "writer")
private List<Post> posts = new ArrayList<Post>();

위와 같이, 고아 객체 자동삭제 기능을 사용하지 않고 작성자(Member)와 게시글(Post)을 매핑하였다고 하자.

 

이 상황에서 아래와 같이 List<Post> post에서 게시글 하나의 참조를 삭제해보자.

Member member = em.find(Member.class, id);	// 특정 ID를 가진 회원 정보 찾아오기
member.getPosts().remove(0);	// 해당 회원 객체의 Posts 리스트에서 0번 인덱스 삭제

그럼 Member 객체에서의 참조만 사라졌을 뿐, Post 데이터는 그대로 데이터베이스에 남아있게 된다.

 

그렇다면 orphanRemoval를 사용한다면 어떻게 될까?

@OneToMany(mappedBy = "writer", orphanRemoval = true)
private List<Post> posts = new ArrayList<Post>();

Member member = em.find(Member.class, id);	// 특정 ID를 가진 회원 정보 찾아오기
member.getPosts().remove(0);	// 해당 회원 객체의 Posts 리스트에서 0번 인덱스 삭제

똑같은 참조 삭제를 진행하였지만, 이번에는 해당 게시글의 데이터 자체가 데이터베이스에서 삭제된다.

 

참조만 삭제하는 것이 아니라 Member 객체 자체를 삭제해도, 그 Member가 참조하던 Post 객체들까지 모두 삭제된다.

이 점은 cascade = CascadeType.REMOVE를 설정했을 때와 동일하다.

'Java 공부 > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

개요  (0) 2023.05.31