JPA - 프록시와 연관관계 관리

노력하는 배짱이·2022년 8월 8일
0

JPA

목록 보기
7/9

1. 프록시

프록시 기초

  1. entityManager.find()
    -> 데이터베이스를 통해서 실제 엔티티 객체 조회
  2. entityManager.getReference()
    -> 데이터베이스 조회를 미루는 프록시(가짜) 엔티티 객체 조회

프록시 특징

  1. 실제 클래스를 상속 받아서 만들어짐

  2. 실제 클래스와 겉 모양이 같음

  3. 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨

  4. 프록시 객체는 실제 객체의 참조를 target에 보관

  5. 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출

프록시 객체의 초기화

val member = em.getReference(Member::class.java, "1L")
member.username

위와 같은 상황에서 member.username 을 호출하면 아래와 같이 초기화가 되어 조회됨 (영속성 컨텍스트에 엔티티가 없을 경우)
1. 프록시 객체가 영속성 컨텍스트에 초기화 요청
2. 영속성 컨텍스트가 DB에서 조회
3. 실제 엔티티 생성
4. 프록시 객체가 실제 엔티티(target)의 메서드(getUsername)를 호출

프록시의 특징

  1. 프록시 객체는 처음 사용할 때 한 번만 초기화

  2. 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님
    -> 초기화 되면 프록시 객체를 통해서 실제 엔티티에 접근 가능

  3. 프록시 객체는 원본 엔티티를 상속받음
    -> 타입 체크시 == 비교하는 것이 아닌 is 비교를 사용해야 함 (instanse of)

  4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, entityManager.getReference()를 호출해도 실제 엔티티를 반환
    -> 프록시 객체가 사전에 있으면, 프록시 객체로 반환

  5. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태(detach, close 등)일 때, 프록시를 초기화하면 예외 발생

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
    -> PersistenceUnitUtil.isLoaded(Object entity)
    -> entityManagerFactory.persistenceUnitUtil.isLoaded(member)

  • 프록시 클래스 확인 방법
    -> member::class.java.name 출력

  • 프록시 강제 초기화 (JPA 표준은 강제 초기화가 없음)
    -> org.hibernate.Hibernate.initialize(entity)
    -> member.username 등으로 강제 호출 해야 함

즉시로딩과 지연로딩

지연로딩

// Member.kt
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
var team: Team
  • Member 엔티티가 로딩이되는 시점에, Team 객체는 프록시 객체로 저장
  • Team 에 대한 조회를 하는 시점에 프록시 객체 초기화가 이루어짐

즉시로딩

// Member.kt
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
var team: Team
  • Member 엔티티가 로딩이되는 시점에, Team 객체도 함께 조회

주의
1. 실무에서는 가급적 즉시로딩이 아닌 지연로딩을 사용
2. JPQL에서 N+1 문제를 일으킴
3. @ManyToOne, @OneToOne 은 기본이 즉시 로딩이라서 LAZY로 설정해야함

영속성 전이(CASCADE)와 고아 객체

영속성 전이(CASCADE)

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들고 싶을 때 사용
    -> @OneToMany(mappedBy = "parent", cascade = [CascadeType.ALL])

  • 단일 엔티티에 종속적일 때 사용
    -> 게시판의 첨부파일 관리

  • ex) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장

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

// Parent.kt
@Entity
class Parent(
    @Id
    @GeneratedValue
    val id: Long? = null,
    var name: String,

    @OneToMany(mappedBy = "parent", cascade = [CascadeType.ALL])
    val childList: MutableList<Child> = arrayListOf()
) {
    fun addChild(child: Child) {
        child.parent = this
        childList.add(child)
    }
}

// Child.kt
@Entity
class Child(
    @Id
    @GeneratedValue
    val id: Long? = null,
    var name: String
) {
    @ManyToOne
    lateinit var parent: Parent
}

// main.kt

val child1 = Child(name = "child1")
val child2 = Child(name = "child2")

val parent = Parent(name = "parent")
parent.addChild(child1)
parent.addChild(child2)


em.persist(parent)
//        em.persist(child1)
//        em.persist(child2)

고아 객체

  • 고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
    -> orphanRemoval = true
    -> @OneToMany(mappedBy = "parent", orphanRemoval = true)

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
    -> 참조하는 곳이 하나일 때 사용해야 함
    -> 특정 엔티티가 개인 소유할 때 사용

  • 개념적으로 부모를 제거하면 자식은 고아가 됨
    -> 고아 객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거되며, CascadeType.REMOVE처럼 동작

영속성 전이 + 고아 객체

  • CascadeType.ALL + orphanRemovel=true
  • 스스로 생명주기를 관리하는 엔티티는 entityManager.persist()로 영속화, entityManager.remove()로 제거
    -> 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음

참고 : 인프런 강의[자바 ORM 표준 JPA 프로그래밍 - 기본편]

0개의 댓글