일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 자동처리
- 객체지향 쿼리 언어
- kusitms
- JPQL
- JPA
- 도메인 주도 개발 시작하기
- delayed message plugin
- Spring
- 자바 ORM 표준 JPA 프로그래밍
- RESTClient
- 교육기획팀
- rabbitmq-delayed-message-exchange
- springboot
- 이펙티브자바
- 30기
- 밋업프로젝트
- scheduling messages with rabbitmq
- Spring Batch
- 최범균
- java
- 영속성
- 큐시즘
- Domain Driven Design
- GitHub Actions
- cicd
- reactive operaton
- 한국대학생it경영학회
- jdbc
- 교육기획팀원
- ddd
Archives
- Today
- Total
코딩은 마라톤
[자바 ORM 표준 JPA 프로그래밍] 8장. 프록시와 연관관계 관리 본문
8.1 프록시
- 엔티티를 조회할 때 연관된 엔티티들이 항상 사용되는 것은 아니다.
- 회원 : 팀 = N : 1
- 회원과 팀의 정보를 출력할 때는 회원과 연관된 팀의 이름을 출력하기 때문에 둘 다 사용한다.
- 하지만 회원의 정보만을 출력할 때는 연관된 팀 엔티티는 전혀 사용하지 않는다. (함께 조회하는 것은 비효율적이다.)
- 따라서 JPA는 이런 문제를 해결하고자 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공한다.
- 이것을 "지연 로딩" 이라 한다.
지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이를 "프록시 객체" 라 한다.
8.1.1 프록시 기초
Member member = em.find(Member.class, "member1");
- 이렇게 엔티티를 직접 조회하면 조회한 엔티티를 실제 사용하든 사용하지 않든 데이터베이스를 조회하게 된다.
- 실제 사용하는 시점까지 조회를 미루고 싶으면 EntityManager.getReference() 메소드를 사용한다.
Member member = em.getReference(Member.class, "member1");
- 이 메소드를 호출할 때 JPA는 데이터베이스를 조회하지 않고 실제 엔티티 객체도 생성하지 않는다.
- 대신에 데이터베이스 접근을 위임한 프록시 객체를 반환한다.
프록시의 특징
- 프록시 클래스는 실제 엔티티 클래스를 상속 받아서 만들어진다. (실제 엔티티 클래스와 겉 모양이 같다.)
- 사용자는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
- 프록시 객체를 초기화하는 것이 실제 엔티티로 바뀌는 것이 아니다. (접근만 할 수 있다.)
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. (준영속 상태의 프록시를 초기화하면 예외가 발생한다.)
프록시의 객체의 초기화
// MemberProxy 반환
Member member = em.getReference(Member.class, "id1");
// 실제 데이터 조회
member.getName();
class MemberProxy extends Member {
Member target = null; // 실제 엔티티를 참조할 필드
public String getName() {
if (target == null) {
// 2. 초기화 요청
// 3. DB 조회
// 4. 실제 엔티티 생성 및 참조 보관
this.target = "실제 엔티티 객체";
}
// 5. target.getName();
return target.getName();
}
- 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
- 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청한다. (초기화)
- 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다. (DB 조회)
- 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관한다.
- 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.
8.1.2 프록시와 식별자
- 엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관한다.
- team.getId();
- @Access(AccessType.PROPERTY)일 경우, 초기화하지 않는다.
- @Access(AccessType.FIELD)일 경우, JPA는 getId() 메소드가 id만 조회하는 메소드인지 다른 필드까지 활용하는지 알지 못하므로 프록시 객체를 초기화한다.
- 프록시는 연관관계를 설정할 때 유용하게 사용할 수 있다.
Member member = em.find(Member.class, "member1");
Team team = em.getReference(Team.class, "team1"); // SQL 실행 X
member.setTeam(team);
- 연관관계를 설정할 때는 식별자 값만 사용하므로 프록시를 사용하면 데이터베이스 접근 횟수를 줄일 수 있다.
- 연관관계 설정시, @Access(AccessType.FIELD)여도 프록시를 초기화하지 않는다.
8.1.3 프록시 확인
- JPA가 제공하는 PersisteneUnitUtil.isLoaded(Object entity) 메소드를 사용하면 프록시의 초기화 여부를 확인할 수 있다.
- 초기화 or 프록시 인스턴스가 아닐 시 true
- 초기화되지 않은 경우 false
boolean isLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity);
// 또는 emf.getPersistenceUnitUtil().isLoaded(entity);
8.2 즉시로딩과 지연로딩
- 즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
- ex) em.find(Member.class, "member1") 호출 시 연관된 팀 엔티티도 함께 조회한다.
- @ManyToOne(fetch = FetchType.EAGER)
- 지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.
- ex) member.getTeam().getName() 처럼 조회한 팀 엔티티를 실제 사용하는 시점에 SQL을 호출해서 팀 엔티티를 조회
- @ManyToOne(fetch = FetchType.LAZY)
8.2.1 즉시 로딩
- 즉시 로딩을 사용하려면 fetch 속성을 FetchType.EAGER로 지정한다.
- 회원을 조회하는 순간 팀도 함께 조회한다.
- 이때 회원과 팀 두 테이블을 조회해야 하므로 쿼리를 2번 실행할 것 같지만, JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다. (회원과 팀을 조인해서 쿼리 한번으로 두 엔티티를 모두 조회한다.)
8.2.2 지연 로딩
- 지연 로딩을 사용하려면 fetch 속성을 FetchType.LAZY로 지정한다.
- 회원을 조회하는 순간 회원만 조회하고 팀은 조회하지 않는다. // em.find(Member.class, "member1");
- 대신 team 멤버변수에 프록시 객체를 넣는다. // Team team = member.getTeam();
- 프록시 객체는 실제 사용될 때까지 데이터 로딩을 미룬다. => 실제 데이터가 필요한 순간에 프록시 객체를 초기화한다.
8.4 영속성 전이: CASCADE
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 "영속성 전이" 기능을 사용한다.
JPA는 CASCADE 옵션으로 영속성 전이를 제공한다.
// 부모 엔티티
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
..
}
// 자식 엔티티
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private Parent parent;
..
}
8.4.1 영속성 전이: 저장
- 부모를 영속화할 때 연관된 자식들도 함께 영속화하기 위해
"cascade = CascadeType.PERSIST" 옵션을 설정한다. - 위와 같이 설정하면 기존에
em.persist(parent);
em.persist(child1);
em.persist(child2); 와 같이 각각 엔티티를 영속 상태롤 만드는 것이 아닌
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
// 연관관계 추가
child1.setParent(parent);
child2.setParent(parent);
parent.getChildren().add(child1);
parent.getChildren().add(child2);
// 부모 저장, 연관된 자식들 저장
em.persist(parent);
8.4.2 영속성 전이: 삭제
- 방금 저장한 부모와 자식 엔티티를 모두 제거하려면 다음과 같이 각각의 엔티티를 하나씩 제거해야 한다.
Parent findParent = em.find(...);
Child findChild1 = em.find(...);
Child findChild2 = em.find(...);
em.remove(findChild1);
em.remove(findChild2);
em.remove(findParent);
- CascadeType.REMOVE로 설정할 경우, 부모 엔티티만 삭제하면 연관된 자식 엔티티도 함께 삭제된다.
Parent findParent = em.find(...);
em.remove(findParent);
8.4.3 CASCADE의 종류
public enum CascadeType {
ALL, // 모두 적용
PERSIST, // 영속
MERGE, // 병합
REMOVE, // 삭제
REFRESH,
DETACH
}
8.5 고아 객체
- JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공한다.
- 이를 "고아 객체(ORPHAN) 제거" 라 한다.
- 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제된다.
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();
...
}
- 고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용되므로 플러시 시점에 DELETE SQL이 실행된다.
- 고아 객체 정리
- 고아 객체 제거는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.
- 참조하는 곳이 하나일 때만 사용해야 한다.
이러한 이유로 orphanRemoval은 @OneToOne, @OneToMany에서만 사용할 수 있다. - CascadeType.REMOVE를 설정한 것과 같다.
8.6 고아 객체
CascadeType.ALL + orphanRemoval = true 를 동시에 사용하면 어떻게 될까?
- 일반적으로 엔티티는 em.persist()를 통해 영속화되고, em.remove()를 통해 제거된다.
즉, 엔티티 스스로 생명주기를 관리한다. - 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.
- 자식을 저장하려면 부모에 등록만 하면 된다. (CASCADE)
- 자식을 삭제하려면 부모에서 제거하면 된다. (orphanRemoval)
'Backend > JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 10장. JPQL (1) (1) | 2024.06.16 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 9장. 값 타입 (0) | 2024.03.17 |
[자바 ORM 표준 JPA 프로그래밍] 7장. 고급 매핑 (0) | 2024.02.22 |
[자바 ORM 표준 JPA 프로그래밍] 6장. 다양한 연관관계 매핑 (1) | 2024.02.12 |
[자바 ORM 표준 JPA 프로그래밍] 5장. 연관관계 매핑 기초 (0) | 2024.02.04 |