프록시
📢 프록시란? 실제 클래스를 상속 받아서 만들어지는 가짜 객체이다. 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다. (이론상 그렇다..) 프록시 객체가 필요한 이유는 불필요한 쿼리가 발생하여 성능 저하가 일어나지 않도록 방지하기 위함이다.
🤔 예를 들어 Member 객체에 멤버변수로 Team 객체가 들어가 있는 경우, 개발자가 Member 를 조회하면 JPA에서는 우선 Member 를 조회하는 쿼리문을 실행시킨다. (“select m from Member”) 그런데 이렇게 가져온 데이터를 Member 객체에 넣을 때 멤버변수에 있는 Team 을 확인하게 되고, Team 에 넣을 객체를 또 다시 가져와야 하므로 두 번째 쿼리문을 실행시킨다. (“select t from Team where t.team_id = ‘xxx’”) 처음부터 Member 와 Team 의 데이터가 필요했다면 상관 없겠지만, Member 의 데이터만 필요한 경우에는 불필요하게 Team 데이터를 확인하는 쿼리문이 발생하는 것이다.
💡 그래서 등장한 개념이 가짜 객체(프록시) 와 지연 로딩이다. Member 의 멤버변수 Team 을 지연 로딩으로 설정하면 개발자가 Team 데이터를 호출하기 전 까지는 JPA 에서 쿼리문을 실행시키지 않는다. 그리고 그 자리(Team객체)에 프록시 객체를 넣는다.
🧩 프록시 객체의 초기화
프록시 객체는 실제 객체의 참조 (target) 를 보관한다.
프록시는 영속성 컨텍스트를 통해 초기화한다. 그리고 딱 한 번만 초기화된다.
# 프록시 초기화 과정
1. 프록시 객체(Team) 는 getTeamName() 요청이 들어오면 (실제 조회가 이루어지면) 영속성 컨텍스트에 초기화 요청을 전달한다.
2. 영속성 컨텍스트가 DB 조회 쿼리를 전달하여 데이터를 가져온다.
3. 가져온 데이터로 실제 Team 엔티티를 생성한다.
4. Team 프록시 객체의 target 에 실제 엔티티 주소가 들어가고, 참조를 통해 Team 엔티티의 getTeamName() 이 실행된다. (target.getTeamName())
프록시 객체를 초기화할 때 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다.
만약 프록시 객체가 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화하면 에러가 발생한다. (org.hibernate.LazyInitializationException)
프록시 확인 메서드 | 방법 |
---|---|
프록시 초기화 여부 확인 | PersistenceUnitUtil.isLoaded(Object entity) |
🧩 즉시 로딩과 지연 로딩
📢 ~ToOne 은 기본적으로 즉시 로딩이고, ~ToMany 는 지연 로딩이다.
어노테이션 | default |
---|---|
@ManyToOne, @OneToOne | 즉시 로딩 |
@OneToMany, @ManyToMany | 지연 로딩 |
지연 로딩 LAZY 를 사용하면 JPA 에서는 프록시로 조회한다.
실제 team 을 사용하는 시점에 DB 조회 쿼리가 나가고 프록시가 초기화된다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
즉시 로딩 EAGER 를 사용하면 프록시를 사용하지 않고 한번에 조회한다.
그렇지만 가급적이면 지연 로딩만 사용하는 것이 좋다. 즉시 로딩을 적용하면 예상치 못한 SQL 이 발생할 수 있기 때문.
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
🧩 영속성 전이 : CASCADE
특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 (자동으로) 영속 상태로 만들고 싶을 때 사용한다.
영속성 전이는 연관관계를 매핑하는 것과 아무런 관련이 없다.
엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
CASCADE 의 종류 | 설명 |
---|---|
ALL | 부모의 모든 변경내용을 자식에게 전부 적용 |
PERSIST | 부모가 영속 상태가 됐을 때만 적용 |
REMOVE | 부모를 삭제할 때 자식도 삭제 |
// CASCADE 사용 예시
@Entity
public class Parent {
...
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private Child child;
}
🧩 고아 객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 ‘고아 객체’ 라고 부른다.
JPA 는 고아 객체를 자동으로 삭제하는 기능을 제공한다.
orphanRemoval = true
참조하는 곳이 하나일 때 사용해야 한다. (특정 엔티티가 개인 소유할 때)
@OneToOne, @OneToMany 만 가능하다.
개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거된다. 즉 CascadeType.REMOVE 처럼 동작한다.