[JPA] 패러다임 불일치와 ORM

객체 모델과 관계형 모델 간 패러다임 불일치(object–relational paradigm mismatch)와 ORM

애플리케이션은 일회성 동작이 목적이 아닌 경우 데이터를 보존하는 것이 필요하다. 애플리케이션의 데이터를 영구적으로 저장하는 것을 데이터 영속화라고 하며 애플리케이션이 가지는 데이터 저장 특성을 영속성(persistence)이라고 한다.

영속성을 위한 기술에는 여러 가지가 있으며 그 중 관계형 데이터베이스는 테이블(또는 릴레이션)이라는 관계형(relational) 모델 개념으로 애플리케이션의 데이터를 영속화(저장)한다. 관계형 모델은 하나의 엔티티(비즈니스의 개념이 담긴 개체)에 관한 데이터를 테이블 하나에 담아 데이터베이스에 저장한다. 행과 열로 이루어진 테이블에 데이터를 저장하며 레코드(또는 튜플)라고 부르는 행은 열의 유일한 속성으로 서로 구별된다. 각 테이블은 하나의 엔티티를 나타내며 한 테이블의 행은 해당 엔티티의 인스턴스를, 열은 인스턴스의 속성을 나타낸다.

관계형 데이터베이스에 데이터를 영속화하고 영속화된 데이터를 조회하기 위해서는 데이터베이스가 제공하는 SQL(Structured Query Language)이라는 인터페이스를 사용해야 한다. SQL 문의 리스트릭션, 프로젝션, 조인을 통한 데이터 조회 결과는 결국 관계형 모델이고 표 형식의 데이터 표현이기 때문에 객체 지향 언어에서 이러한 데이터 조회 결과를 사용하기 위해서는 패러다임 불일치 문제를 해결해야 하며 ORM은 여러 대안 중 하나이다.

ORM(Object Relational Mapping)은 객체 모델과 관계형 모델을 서로 매핑하는 것을 의미한다. ORM은 매핑을 위한 메타데이터를 이용하여 객체 지향 언어의 객체를 관계형 데이터베이스의 테이블로 영속화하는 것과 반대로 영속화된 테이블의 데이터를 객체로 인스턴스화하는 것을 자동화해준다.


객체 모델과 관계형 모델의 데이터 조회

객체 지향 언어와 관계형 데이터베이스는 데이터를 표현하는 방법이 서로 다르기 때문에 조회하는 과정도 다르다. 객체 모델에서는 객체 간 참조 관계를 사용하여 데이터를 조회하는 반면, 관계형 모델에서는 테이블(릴레이션)이라는 데이터 구조를 통해 데이터를 조회한다. 따라서 객체(클래스) 개념과 테이블 개념을 서로 매핑하는데에는 개념적, 기술적 어려움이 존재한다.

객체 간 참조 탐색을 통해 데이터를 조회하는 방법을 사용하여 데이터베이스에서 데이터를 조회하는 것은 하나의 테이블에서 데이터를 조회하여 엔티티 객체에 매핑한 후 다른 테이블과 연관된 외래키를 가지고 다시 테이블에서 데이터를 조회하여 엔티티 객체에 매핑하는 비효율적인 방법이며 SQL 문을 여러 번 실행하므로 성능에 좋지 않은 영향을 미칠 수 있다. 따라서 관계형 데이터베이스의 조인 개념을 사용하여 데이터 조회를 위한 SQL 문 실행 횟수를 줄이고 한 번의 SQL 문 실행을 통해 관련된 여러 엔티티 객체들을 한 번에 매핑하여 데이터를 조회하는 것이 효율적이다. 이는 ORM 프레임워크로 인한 성능 문제 해결을 위한 방법들이기도 하다.


JPA에서 엔티티 간 연관관계 설정 시 발생할 수 있는 문제점과 해결 방안

  1. 엔티티 연관관계가 양방향 관계이면 순환 참조 발생 및 무한 루프 발생 가능
    • 원인: 객체를 주입 받아 필드에 설정하는 수정자 메서드나, 컬렉션에 원소를 추가하는 메서드와 같은 편의 메서드를 각각의 엔티티에 모두 정의하였고 서로 호출하는 경우 발생
    • 해결 방법: 서로의 편의 메서드를 계속 호출하는 무한 루프에 빠지지 않도록 엔티티를 확인하는 코드를 작성해야 함
      • 객체 참조 전 객체가 이미 참조되어 있는지 확인
      • 컬렉션에 추가하기 전 컬렉션에 이미 추가되어 있는지 확인
  2. 조회 시 엔티티 일대다 또는 다대일 연관관계를 사용할 경우(객체 그래프 탐색 이용) n+1 문제(n+1 query problem) 및 성능 문제 발생 가능
    • 원인
      1. 즉시 로딩이며 페치 조인을 사용하지 않는 JPQL 조회: 일대다 연관관계이고 @OneToMany 필드(연관 엔티티 컬렉션 필드)에 즉시 로딩 설정이 되어 있는 경우 JPQL을 통해 엔티티(1)만 조회(단일 조회 또는 컬렉션 조회) 시 연관 엔티티(n) 컬렉션도 조회하기 위해 SQL 문이 n번 더 수행됨
        • 연관 엔티티의 총 개수 만큼 SQL 문이 추가로 수행됨
      2. 지연 로딩이며 JPQL을 사용하였지만 이후 컬렉션 엔티티를 전체 조회: 1번과 동일한 상황에서 지연 로딩 설정이 되어 있는 경우 JPQL을 통해 엔티티(1)만 조회(단일 조회 또는 컬렉션 조회) 시 연관 엔티티(n) 컬렉션은 조회하지 않으므로 SQL 문은 한 번만 실행되지만, 이후에 get() 메서드로 연관 엔티티 컬렉션의 모든 엔티티를 조회하는 경우 SQL 문이 n번 더 수행됨
    • 해결 방법
      1. JPQL을 사용하지 않고 조회: 일대다 연관관계이고 @OneToMany 필드(컬렉션 필드)에 즉시 로딩 설정이 되어 있는 경우 find() 메서드로 엔티티(1)만 조회(단일 조회 또는 컬렉션 조회) 시 조인을 통해 연관 엔티티(n) 컬렉션도 한 번에 조회하므로 SQL 문은 한 번만 실행됨
      2. JPQL의 페치 조인을 사용하여 조회: 페치 조인을 사용하면 엔티티 조회 시 조인을 이용해서 연관된 엔티티도 한 번에 조회하므로 SQL 문은 한 번만 실행됨
      3. 하이버네이트가 제공하는 해결 방법 사용:

Comments