[JPA BASIC] 3. Management

프록시란?

JPA는 지연로딩을 지원하기 위해 가짜 객체(프록시)를 사용한다. 프록시 객체는 실제 엔티티 클래스를 상속하여 생성되는 가짜 객체이며, 애플리케이션이 실제 데이터에 접근해야 할 필요성이 생길 때까지 데이터 로딩을 미루게 되어, 성능 최적화에 기여하게 된다.

  • em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
    • (영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환)

즉시 로딩 VS 지연 로딩

JPA에서 즉시로딩(Eager Loading)과 지연로딩(Lazy Loading)은 엔티티의 관계된 데이터를 언제 불러올지 결정하는 두 가지 전략.
즉시로딩은 사용의 편리함을 제공하지만, 불필요한 데이터 로딩으로 인해 성능 문제를 일으킬 수 있으며, 지연로딩은 성능 최적화를 위해 사용되지만, 관리가 복잡해질 수 있다.

  • 즉시로딩(Eager Loading): 엔티티를 조회할 때, 연관된 모든 데이터를 즉시 불러온다. 이 방식은 필요하지 않은 데이터까지 불러올 수 있어 성능 저하의 원인이 될 수 있다. (@ManyToOne과 @OneToOne 관계에서 기본 전략)
  • 지연로딩(Lazy Loading): 엔티티를 조회할 때, 연관된 데이터는 실제로 접근하는 순간에 불러온다. 이 방식은 필요할 때만 데이터를 불러와 성능 최적화에 도움을 준다. (@OneToMany와 @ManyToMany 관계에서 기본 전략)

주의

  • 가급적 지연 로딩만 사용(특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
// 지연 로딩
@Entity
public class Member {
    @Id
    @GeneratedValue 
    private Long id;

    @Column(name = "USERNAME") 
    private String name;

    @ManyToOne(fetch = FetchType.LAZY) //** 
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    ..
}

// 즉시 로딩
@Entity
public class Member {
    @Id
    @GeneratedValue 
    private Long id;

    @Column(name = "USERNAME") 
    private String name;

    @ManyToOne(fetch = FetchType.EAGER) //** 
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    ..
}

영속성 전이: CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들도 싶을 때
  • 예: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장.
    // 설정 방법
    @OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)

    // 설정 전
    ...
    Child child1 = new Child();
    Child child2 = new Child();

    Parent parent = new Parent();
    parent.addchild(child1);
    parent.addchild(child2);

    em.persist(parent);
    // 자식들도 저장해 줘야 한다.
    em.persist(child1);
    em.persist(child2);
    ...


    // 설정 후
    ...
    Child child1 = new Child();
    Child child2 = new Child();

    Parent parent = new Parent();
    parent.addchild(child1);
    parent.addchild(child2);

    em.persist(parent);
    ...

옵션 종류

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

주의

  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐

고아 객체

  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 가리킨다.
    • 부모가 제거될때, 부모와 연관되어있는 모든 자식 엔티티들은 고아객체가 된다.
    • 부모 엔티티와 자식 엔티티 사이의 연관관계를 삭제할때, 해당 자식 엔티티는 고아객체가 된다.

orphanRemoval = true

옵션이 활성화되면, 부모 엔티티에서 자식 엔티티의 참조가 제거될 때 자식 엔티티도 데이터베이스에서 자동으로 삭제된다. 즉, 더 이상 참조하는 부모가 없는 고아 객체를 자동으로 제거 함.

주의

  • 다른 곳에서도 참조하지 않는지 확인!
  • @OneToOne, @OneToMany만 가능
@Entity
public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Person> members = new ArrayList<>();

    // 생성자, getter, setter 생략

    public void addMember(Person person) {
        members.add(person);
        person.setTeam(this);
    }

    public void removeMember(Person person) {
        members.remove(person);
        person.setTeam(null);
    }
}
@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    // 생성자, getter, setter 생략
}

영속성 전이 + 고아 객체, 생명주기

  • CascadeType.ALL + orphanRemoval=true
  • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음

차이점

orphanRemoval=true와 CascadeType.REMOVE의 주된 차이점 중 하나는 고아 객체(Orphaned objects)의 처리 방식에 있다.
CascadeType.REMOVE는 부모 엔티티가 삭제될 때 이와 연결된 자식 엔티티들도 함께 삭제. 하지만 이 설정은 부모와 자식 간의 연관 관계가 끊어진 경우(예를 들어, 외래키를 null로 설정)에는 자식 엔티티가 자동으로 삭제되지 않는다.
반면, orphanRemoval=true 설정은 부모 엔티티로부터 자식 엔티티가 분리(즉, 컬렉션에서 제거되거나 연관 관계가 null로 설정됨)될 경우, 해당 자식 엔티티를 고아로 간주하고 데이터베이스에서 자동으로 삭제 한다.


© 2023 Lee. All rights reserved.