ORM

ORM

Object Relational Mapping (객체 관계 매핑)

개념

  • 프로그래밍 언어의 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 도구

  • 객체와 데이터베이스 테이블의 매핑을 통해 엔티티 클래스 객체 안에 포함된 정보를 테이블에 저장하는 기술

  • 프로그래밍 언어와 관계형 데이터베이스 사이의 중계자 역할

사용 이유

  • 객체모델과 관계형 모델 간의 불일치를 ORM을 통해 해결

    • 불일치 발생

      • 객체지향 프로그래밍은 클래스를, 관계형 데이터베이스는 테이블을 사용하기에 불일치 발생

    • 불일치 해결

      • 객체 간의 관계를 바탕으로 SQL을 자동으로 생성해 해결

      • SQL문을 직접 작성하지 않고 엔티티를 객체로 표현

      • SQL문을 따로 작성할 필요 없이, 객체를 통해 간접적으로 데이터베이스 조작

  • 데이터베이스와 프로그래밍 언어 사이 간극을 줄임

  • 느슨하게 연결된, 테스트에 용이한 애플리케이션 제작가능

객체 관계간 불일치

불일치설명

세분성(Granularity)

경우에 따라서 데이터베이스에 있는 테이블 수보다 더 많은 클래스를 가진 모델이 생길 수 있음

상속성(Inheritance)

RDBMS는 객체지향 프로그래밍 언어의 특징인 상속 개념이 없음

일치(Identity)

RDBMS는 기본키(primary key)를 이용하여 동일성을 정의. 자바는 객체 식별(a==b) 과 객체 동일성(a.equals(b)) 을 모두 정의

연관성(Associations)

객체지향 언어는 방향성이 있는 객체의 참조를 사용하여 연관성을 나타내는 반면, RDBMS는 방향성이 없는 외래키를 이용해 나타냄

탐색(Navigation)

자바는 그래프형태로 하나의 연결에서 다른 연결로 이동하며 탐색하는 반면, RDBMS는 일반적으로 SQL문을 최소화하고 JOIN 을 통해 여러 엔티티를 로드하여 원하는 대상 엔티티를 선택하는 방식으로 탐색

장점

  • 직관적 코드와 비즈니스 로직 집중 가능

    • SQL Query가 아닌 메서드로 데이터 조작 가능

    • 선언문, 할당, 종료와 같은 부수적인 코드가 줄어듦

    • 객체들에 대한 코드를 별도로 작성하여 코드 가독성 향상

    • SQL의 절차적 접근이 아닌, 객체지향적 접근으로 생산성 향상

  • 재사용 및 유지보수의 편리성 증가

    • ORM은 독립적으로 작성되며, 해당 객체들을 재활용 가능

    • 디지인 패턴을 견고하게 만드는데 유리

  • DBMS에 대한 종속성 감소

    • RDBMS와 프로그래밍 언어의 객체 모델 사이 간격 좁힘

    • 대부분의 ORM 솔루션은 DB에 종속적이지 않음

단점

  • 완벽한 ORM만으로 서비스 구현이 어려움

    • 프로젝트의 복잡성이 커질 경우 난이도 증가

    • 잘못된 구현시 속도 저하, 일관성 무너짐 같은 문제 발생

    • 자주 사용되는 대형 쿼리 경우, 속도를 위해 별도 튜닝이 필요할 수 있음

  • 프로시저가 많은 시스템에선 ORM의 객체 지향적인 장점활용이 어려움

프로시저(Procedure) : 특정 로직을 처리만하고 결과 값을 반환하지 않는 서브 프로그램

엔티티의 생명주기

  • 비영속 (new/transient)

    • 엔티티 객체를 생성만 하고 영속성 컨텍스트에 저장하지 않은 상태

    • DB와 연결이 안되어있는 순수 객체 상태

  • 영속 (managed)

    • 영속성 컨텍스트에 저장된 상태

  • 준영속 (detached)

    • 영속성 컨텍스트에 저장되었다가 분리된 상태

  • 삭제 (removed)

    • 삭제된 상태

영속성 (Persistence)

  • 개념

    • 데이터를 생성한 프로그램이 종료되더라도 사라지지 않는 데이터의 특성

  • 특징

    • 영속성을 갖지 않는 데이터는 메모리에만 존재하기에, 프로그램을 종료하면 모두 잃어버림

  • Object Persistence (영구적인 객체)

    • 메모리 상의 데이터를 영구적으로 저장하여 영속성 부여

    • 파일 시스템, 관계형 데이터베이스, 객체 데이터베이스 등을 활용하여 저장

    • 자바에서 데이터를 데이터베이스에 저장하는 방법

      • JDBC

      • Spring JDBC

      • Persistence Framework

Persistence Framework 는 일반적으로 SQL Mapper와 ORM으로 나눠지며 Mybatis는 SQL Mapper에, Hibernate는 ORM에 속한다.

영속성 컨텍스트(Persistence Context)

개념

  • 엔티티를 영구 저장하는 환경

  • Server side와 Database 사이에 엔티티를 저장하는 논리적인 영역

  • 애플리케이션과 DB사이 객체를 보관하는 가상의 DB 역할

  • Entity Manager를 통해 엔티티를 저장하거나 조회 시, Entity Manager는 영속성 컨텍스트에 엔티티를 보관하고 관리

특징

  • Map 객체로 엔티티 저장

    • 엔티티를 식별자 값(@Id 맵핑)으로 구분

    • Key-value로 관리하는데 이때 key 값이 @Id 값이 됨

  • 식별자 값 필요

    • 영속상태의 엔티티는 반드시 식별자 값이 있어야 함

  • 동일 객체 반환

이점 영속성 컨텍스트가 엔티티를 관리할때의 이점

  • 1차 캐시

    • 영속성 내부엔 1차 캐시가 존재

    • 엔티티 조회 시, 1차 캐시에 엔티티가 존재한다면 DB를 찾아보지 않아도 됨

  • 영속 엔티티의 동일성 보장

    • 같은 객체를 반환하게 되면 새로운 객체가 나오는 것이 아닌 동일간 객체 반환

    • 1차 캐시로 반복 가능한 읽기(Repeatable Read) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 어플리케이션 차원에서 제공

  • 트랜잭션을 지원하는 쓰기 지연

    • 영속성 컨텍스트는 트랜잭션 범위 안에서 동작하므로, 트랜잭션이 끝나야 Commit이 이뤄지고 반영 됨

    • 쓰기 지연 : SQL쿼리들을 모았다가 flush (영속성 컨텍스트의 변경내용을 DB에 반영할 때) 모아둔 쿼리들을 모두 날림

  • 변경 감지 (Dirty Checking)

    • 영속성 컨텍스트 내 스냅샷과 엔티티를 비교하여 변경된 엔티티가 있으면 Update 쿼리 자동 생성

    • Update 쿼리 또한 쓰기 지연 될 수 있음

  • 지연 로딩 (Lazy Loading)

    • 엔티티와 관계가 맺어진 엔티티의 데이터를 가져올 수 있음

    • 연관관계 매핑되어있는 엔티티를 조회 시, 우선 프록시를 반환하고 실제로 필요할때 쿼리를 날려 가져오는 기능

    • 성능저하의 원인이 될 수 있음

JPA

개념

  • 자바 ORM에 대한 API 표준명세

    • 인터페이스의 모음으로 구현체가 없음

    • 사용을 위해 ORM프레임워크 이용

      • Hibernate

      • Spring DAO

      • Enterprise JavaBeans Entity Beans

특징

  • JPA에서는 테이블과 매핑되는 엔티티 객체 정보를 영속성 컨텍스트를 통해 애플리케이션 내에서 오래 지속되도록 보관

  • JPA는 애플리케이션과 DBMS 사이 인터페이스 역할을 해줌으로, 개발자는 JPA 인터페이스에 맞춰 구현되어있는 기능 사용

동작

  • 애플리케이션과 JDBC 사이에서 동작

  • JPA 내부에서 JDBC API를 사용하여 SQL을 호출하고 DB와 통신

사용이유

  • SQL이 아닌 객체 중심 개발 가능

    • 생산성과 유지보수성 향상

  • 패러다임 불일치 해결

    • 상속문제 해결

      • 객체는 상속이 있지만 테이블은 상속이 없음 (유사한 슈퍼타입-서브타입 존재)

      • JDBC에 상속객체를 저장하려면 부모와 자식 객체용 INSERT 따로 작성과 JOIN을 통해 결과를 얻어야함

    • 연관관계 문제 해결

      • 객체는 참조를 통해 연관관계를 가지고, 참조에 접근해 연관된 객체 조회

        • 단방향 참조 (참조가 있는 방향으로만 참조가능)

      • 테이블은 외래키를 사용해 다른 테이블과 연관관계를 가지고 조인을 통해 테이블 조회

        • 양방향 참조

  • 객체 그래프 탐색 문제 해결

    • SQL을 직접다룰 경우, SQL 조회시 처음 SQL에 따라 탐색범위가 저장되기에 제약 발생

  • 비교 문제 해결

    • 데이터베이스는 기본 키값으로 구분하나, 객체는 동일성(==), 동등성(equals()) 두가지 비교 방법 존재

    • 같은 행이여도 객체로 볼때 다른 인스턴스를 반환하는 문제 해결

JPA는 연관된 객체를 사용하는 시점에 SQL을 전달할 수 있으며, 같은 트랜잭션 내에서 조회할 때 동일성도 보장하기에 위와같은 문제 해결가능

N+1문제

  • N+1문제란?

    • 연관관계가 설정된 엔티티를 조회할 경우, 조회된 데이터 개수(n)만큼 연관된 엔티티를 조회하기 위해 추가적인 쿼리가 발생하는 문제

    • 1 : 한 엔티티를 조회하기 위한 쿼리의 개수

    • N : 조회된 엔티티 개수만큼 연관된 데이터를 조회하기 위한 추가적인 쿼리 개수

발생 상황

  • 언제 발생?

    • JPA Repository를 활용해 인터페이스 메소드를 호출할 시 발생

  • 누가 발생?

    • 1:N 또는 N:1 관계를 가진 엔티티를 조회할 때 발생

  • 어떤 상황에 발생?

    • JPA Fetch 전략이 즉시 로딩 전략으로 데이터를 조회하는 경우

    • JPA Fetch 전략이 지연 로딩 전략으로 데이터를 가져온 이후에 연관 관계인 하위 엔티티를 다시 조회하는 경우

  • 왜 발생?

    • JPA Repository로 find 시 실행하는 첫 쿼리에서 하위 엔티티까지 한 번에 가져오지 않고, 하위 엔티티를 사용할 때 추가로 조회 하기 때문

    • JPQL은 기본적으로 글로벌 Fetch 전략을 무시하고 JPQL만 가지고 SQL을 생성하기 때문

  • 즉시 로딩의 경우

    1. JPQL에서 만든 SQL을 통해 데이터 조회

    2. 이후 JPA에서 Fetch 전략을 가지고 해당 데이터의 연관관계인 하위 엔티티들을 추가 조회

    3. 2번 과정으로 인해 N+1문제 발생

  • 지연 로딩의 경우

    1. JPQL에서 만든 SQL을 통해 데이터 조회

    2. JPA에서 Fetch 전략을 가지지만, 지연 로딩이기 때문에 추가 조회는 하지 않음

    3. 하지만 하위 엔티티를 가지고 작업하게 되면 추가 조회가 발생하기 때문에 결국 N + 1 문제 발생

  • @OneToOne에서의 발생

    • 연관관계의 주인이 아닌 엔티티를 조회하는 경우, 지연 로딩으로 설정했더라도 연관된 엔티티를 즉시 로딩으로 조회

    • fetch join@EntityGraph으로 해결

  • @ManyToOne에서의 발생

    • fetch join@EntityGraph으로 해결

    • @BatchSize로도 해결 가능 (쿼리 한번 더 발생)

해결 방법

  • Fetch Join

    • N+1 자체가 발생하는 이유는 한쪽 테이블만 조회하고 연결된 다른 테이블은 따로 조회하기 때문

    • 미리 두 테이블을 JOIN 하여 한 번에 모든 데이터를 가져올 수 있다면 애초에 N+1 문제가 발생하지 않을 것이란 개념에서 나온 방법

    • 두 테이블을 JOIN하는 쿼리 직접 작성

    • 단점

      • 쿼리 한번에 모든 데이터를 가져오기 때문에 JPA가 제공하는 Paging API 사용 불가능(Pageable 사용 불가)

      • 1:N 관계가 두 개 이상인 경우 사용 불가

      • 패치 조인 대상에게 별칭(as) 부여 불가능

      • 번거롭게 쿼리문을 작성해야 함

  • @Entity Graph

    • Fetch join과 동일하게 JPQL을 사용해 Query문을 작성하고 필요한 연관관계를 EntityGraph에 설정

  • Batch Size

    • JPA의 Batch Size 기능을 통해 Batch Size를 설정해둠

    • JPA에서 지연로딩을 할 때, 한번에 최대 Batch Size만큼의 엔티티를 Where절에 in으로 가져옴

    • 엔티티만 가져오기에(Join 진행X) 테이블의 Row가 뻥튀기 되지 않음

    • N+1문제가 완벽히 해결되는 것은 아니나, 많은 쿼리가 줄어들기에 실용적인 방법

    • Lazy Loading과 같이 사용하면, 실제 연관관계 엔티티가 필요한 시점에 Batch Size를 통해 최소한의 쿼리를 보내 효율적으로 Lazy Loading 가능

Fetch join의 경우 inner join을 하는 반면, EntityGraph는 outer join을 기본으로 함

카테시안 곱 문제 FetchJoin과 EntityGraph는 공통적으로 카테시안 곱(Cartesian Product)이 발생 하여 중복이 생길 수 있기에 주의해야 함

이는 JPQL에 DISTINCT 추가, OneToMany 필드 타입을 Set으로 선언하는 방법을 통해 중복을 제거할 수 있다.

카테시안 곱 : 두 테이블 사이에 유효 join 조건을 적지 않았을 때 해당 테이블에 대한 모든 데이터를 전부 결합하여 테이블에 존재하는 행 갯수를 곱한만큼의 결과 값이 반환되는 것

Last updated