자바 ORM 표준 JPA 프로그래밍(기본편) - 프록시와 연관관계 관리

최준석·2022년 11월 18일
0

프록시와 연관관계 관리


목차

  • 프록시
  • 즉시로딩과 지연로딩
  • 지연로딩 활용
  • 영속성 전이:CASCADE
  • 고아객체
  • 영속성전이 + 고아객체, 생명주기

1. 프록시

프록시 기초

em.find() vs em.getReference()

em.find()로 데이터베이스를 통해 실제 엔티티 객체를 조회할 수 있으며 em.getReference()로 데이터베이스 조회 없이 가짜(프록시)엔티티 객체를 조회할 수 있다.

프록시 특징

실제 클래스를 상속받아서 만들어지고, 실제 클래스와 겉모양이 같으며, 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분없이 사용하면 된다. 프록시객체는 실제객체의 참조(target)을 보관하고 있으며, 프록시객체를 호출하면 프록시객체는 실제객체의 메소드를 호출해 실제값을 가져온다.

프록시객체의 초기화

프록시 객체는 em.getReference(객체.class, "") 메소드를 이용해 초기화 할 수 있고, 해당 프록시객체를 호출할 때 실제로 데이터베이스에서 해당 엔티티 객체 관련 테이블을 가져온다.

프록시의 특징

프록시 객체는 처음 사용할 때 단 한번의 초기화 과정을 거치며, 초기화 할 때는 원본 엔티티를 상속받은 객체의 형태로 새로운 객체가 생성된다. 초기화 할 때 영속성 컨텍스트에 해당 엔티티가 이미 존재한다면, em.getReferance()를 호출해도 해당 엔티티를 호출한다. 그리고 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화 하게되면 예외가 발생한다.

프록시 확인

PersistenceUnitUtil.isLoaded(Object entity) 를 통해서 프록시 인스턴스의 초기화 여부를 확인할 수 있으며, entity.getClass().getName()을 통해서 프록시의 클래스를 확인할 수 있다. 그리고 org.hibernate.Hibernate.initialize(entity)를 통해서 프록시를 강제로 초기화 할 수 있다.

  • 참고 : JPA 표준은 강제 초기화가 없다.

2. 즉시로딩과 지연로딩

지연로딩

Member 엔티티를 사용할 때 해당 엔티티의 Team관련 정보까지는 필요없는 경우가 있다. 이 때 FetchTypeEager로 설정하게 되면 N+1 문제가 발생하는데 Lazy로 설정해서 이런 문제를 방지할 수 있다.

예시)

@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;
	..
}

  • FetchType.Lazy로 설정하면 연관 테이블이 프록시로 생성된 후, 해당 테이블을 호출할 때 DB에서 읽어온다.

즉시로딩

위의 경우와 다르게 Member와 Team을 자주 함께 사용하는 경우가 있다. 이 경우에는 Member를 불러올때 연관 테이블인 Team을 같이 조회하는 방식이 선호되는데, FetchType.Eager을 통해 즉시로딩을 설정할 수 있다.

예시)

@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;
..
}

  • JPA 구현체는 가능하면 조인을 사용해서 SQL을 한번에 조회한다

프록시와 즉시로딩 주의점

즉시로딩을 적용하게 되면 JPQL에서 N+1문제를 일으키는 등 예상치 못한 SQL문제들이 발생하므로 실무에서는 가급적 지연로딩만 사용하는게 좋다.

  • @ManyToOne, @OneToOne은 기본이 즉시로딩이므로 지연로딩으로 바꿔주자.
  • @OneToMany, @ManyToMany는 기본이 지연로딩이다

3. 지연로딩 활용

Member와 Team은 자주 함께 사용하므로 -> 즉시로딩
Member와 Order는 가끔 함께 사용하므로 -> 지연로딩
Order와 Product는 자주 함께 사용하므로 -> 즉시로딩

실무에서는 모든 연관관계에 지연로딩을 사용하는게 좋다. 즉시로딩이 필요할 경우에는 JPQL fetch 조인이나, 엔티티 그래프 기능을 활용하는게 좋다.

4. 영속성 전이:CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶으면 영속성 전이(CASCADE)를 사용하면 된다.

예시)

부모 엔티티를 저장할 떄 자식 엔티티도 함께 저장

@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)

주의)

  • 영속성 전이는 연관관계와 관계없다.
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐임

CASCADE의 종류

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

5. 고아객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아객체라고 하는데, orphanRemoval=true 설정으로 이 고아객체를 자동으로 삭제하게끔 할 수 있다.

예시)

Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거

위의 코드를 실행하게 되면 List의 0번째에 있는 Children객체가 삭제되게 된다.

주의)

고아객체 제거 기능은 참조가 제거된 엔티티를 다른곳에서 참조하지 않는 고아객체로 보고 삭제하는 기능인데, 참조하는 곳이 하나일때만 사용해야 한다.(참조하는곳이 여러곳일 때 사용하게 되면 관리도 어렵고 의도하지 않은 상황이 발생할 수 있다) 즉, @OneToOne이나 @OneToMany일 경우에만 사용 가능하다.

참고)

개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거된다. 마치 CascadeType.REMOVE처럼 동작한다.

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

CascadeType.ALL + orphanRemoval=true를 설정하게 되면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다. 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용하다.

출처 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
(자바 ORM 표준 JPA 프로그래밍 - 기본편)

profile
Back-End Web Developer

2개의 댓글