프록시란,
엔티티를 조회할 때 연관관계로 매핑되어 있는 엔티티들이 항상 사용되는 것은 아니다.
연관관계의 엔티티는 비즈니스 로직에 따라 사용될 때도 있지만 그렇지 않을 때도 있다.
Member의 Team이 과연 항상 사용 될까?
JPA는 이런 문제를 해결하려고 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하는데 이것을 지연 로딩이라 한다. 그런데 지연 로딩 기능을 사용하려면 실제 엔티티 객체 대상에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라 한다.
우리는 이제까지 select문을 출력하기 위해 EntityManager.find()
메소드를 사용했다.
하지만 프록시 객체를 생성하기 위해서 EntityManager.getReferance()
메소드를 사용한다.
EntityManager.getReferance()
메소드를 쓰니..? 어쩌구 프록시라는 객체가 생성되었다.
무슨 차이냐 할수도 있겠지만, 가장 중요한 차이는 select 쿼리가 나가지 않았다는 것.
이 프록시 객체는 실제 객체에 대한 참조(target)를 보관한다. 그리고 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다. 이를 프록시 객체 초기화라 한다.
무슨말이냐하면.. 이 가져온 프록시 객체를 실제로 사용하려고 할 때, 쿼리를 날려서 실제 객체를 가져오게 된다.
프록시 객체가 생성된 이후에 select 쿼리가 나감을 볼 수 있다.
org.hibernate.LazyInitializationException
예외를 발생시킨다.그렇다면 이 프록시를 어떻게 이용할 수 있을까??
위의 Member와 Team 처럼 Member를 불러왔는데 Team을 사용하지 않을 것이라면, 굳이 그 정보를 가져오지 않는 것이 더 성능이 좋을것이다.
이러한 기법을 지연 로딩, 이제까지 해온 것이 즉시 로딩이다.
즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.
즉시 로딩은 지금 껏 해왔던 것이라 별다를 것은 없다.
별개로 조인에 대해 알아보자.
즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다.
여기서는 Member과 Team을 조인해서 쿼리 한번으로 두 엔티티를 모두 조회한다.
현재 Member 테이블에 TEAM_ID 외래 키는 NULL 값을 허용하고 있다면, Team에 소속되지 않은 Member가 있을 가능성이 있다. Team에 소속하지 않은 Member과 Team을 내부 조인 하면 Team은 물론이고 Member 데이터도 조회할 수 없다.
하지만 외부 조인보다 내부 조인이 성능과 최적화에서 더 유리하다.
내부 조인을 사용하려면 어떻게 해야 할까?
외래 키에 NOT NUL
제약 조건을 설정하면 값이 있는 것을 보장한다. NOT NULL
을 표현하는 방법은 두 가지가 있다.
@JoinColumn(name = "TEAM_ID", nullable = false)
@ManyToOne(fetch = FetchType.EAGER, optional = false)
정리하자면 JPA는 선택적 관계면 외부 조인을 사용하고 필수 관계면 내부 조인을 사용한다.
하하. 위에서 간략하게 보았다.
이번 글은 구조를 잘 못 짠거같다.. 하지만 오늘은 너무 피곤하므로..
@ManyToOne, @OneToOne
은 기본이 즉시 로딩 -> fetch = FetchType.LAZY
속성 사용!@OneToMany, @ManyToMany
는 기본이 지연 로딩 -> fetch = FetchType.EAGER
로 즉시 로딩 설정 가능특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 cascade 속성을 이용할 수 있다.
일대다 관계의 Parent와 Child가 있을 때, 기존까지는 아래와 같이
일일히 영속성 컨테이너에 올려주어야 했다.
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.persist(child1);
em.persist(child2);
하지만 이러한 관계를 cascade 속성을 이용해서 아래와 같이 수정할 수 있다.
매핑을 할때 cascade 속성을 이용하면, 편리하게 영속성 관리를 할 수있다.
참고로 CascadeType.PERSIST, CascadeType.REMOVE
는 em.persist(), em.remove()
를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생한다.
고아 객체란, 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 말한다.
위 예제를 보면 Parent가 사라진 Child를 말할 수 있다.
이럴때 orphanRemoval = true
속성을 사용해서 고아 객체가 된 엔티티를 자동으로 삭제 되게 조절할 수 있다.
이 때, 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로
보고 삭제하는 기능이기 때문에 참조하는 곳이 하나일 때 사용해야한다.
개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고 아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE
처럼 동작한다.
@OneToOne, @OneToMany
에만 사용 가능하다.
CascadeType.ALL + orphanRemovel=true
를 통해
일반적으로 엔티티는 EntityManager.persist()
를 통해 영속화되고 EntityManager.remove()
를 통해 제거된다. 이것은 엔티티 스스로 생명주기를 관리한다는 뜻이다. 그런데 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다
도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용하다.