본문 바로가기
JPA (ORM)

객체와 관계형 데이터베이스, 그 사이의 간극: 임피던스 불일치와 ORM

by daddydontsleep 2026. 7. 23.
728x90
728x90

사진: Unsplash 의 Boxed Water Is Better

 

 

들어가며

백엔드 개발을 하다 보면 자연스럽게 두 가지 세계를 오가게 됩니다. 하나는 클래스와 인스턴스로 이루어진 객체지향의 세계, 다른 하나는 테이블과 row로 이루어진 관계형 데이터베이스의 세계입니다. 이 두 세계는 데이터를 표현하는 방식이 근본적으로 다르기 때문에, 둘을 연결하려는 순간부터 크고 작은 마찰이 생깁니다. 이 글에서는 그 마찰이 왜 생기는지, 그리고 오늘날 우리가 어떻게 그 간극을 메우고 있는지 정리해 보겠습니다.

 

객체와 테이블, 애초에 다른 존재

객체는 데이터(속성)와 그 데이터를 다루는 동작(메서드)을 하나로 묶은 단위입니다. 반면 관계형 데이터베이스의 테이블은 행과 열로 구성된 순수한 데이터 저장 구조이며, 동작이라는 개념이 없습니다.

public class Member {
    private Long id;
    private String name;
    private Team team;

    public void changeTeam(Team newTeam) {
        this.team = newTeam;
    }
}
CREATE TABLE MEMBER (
    ID BIGINT PRIMARY KEY,
    NAME VARCHAR(255),
    TEAM_ID BIGINT REFERENCES TEAM(ID)
);

겉보기에는 비슷해 보이지만, Member 객체는 changeTeam()이라는 행위를 스스로 가지고 있는 반면 MEMBER 테이블은 그저 값을 저장할 뿐입니다. 이 차이에서부터 여러 구조적 불일치가 시작됩니다.

 

임피던스 불일치가 발생하는 지점들

1. 상속

객체는 상속 구조를 자연스럽게 표현합니다.

abstract class Item {
    Long id;
    String name;
    int price;
}

class Book extends Item {
    String author;
    String isbn;
}

class Album extends Item {
    String artist;
}

하지만 테이블에는 상속이라는 개념이 없습니다. 이를 표현하려면 개발자가 직접 전략을 선택해야 합니다.

-- 단일 테이블 전략
CREATE TABLE ITEM (
    ID BIGINT PRIMARY KEY,
    DTYPE VARCHAR(31),
    NAME VARCHAR(255),
    PRICE INT,
    AUTHOR VARCHAR(255),
    ARTIST VARCHAR(255)
);

 

2. 연관관계의 방향

객체는 참조를 통해 단방향으로 다른 객체를 탐색합니다.

Team team = member.getTeam(); // Member → Team, 단방향

반대로 테이블은 외래키 하나로 양방향 조회가 모두 가능합니다. 방향이라는 개념 자체가 테이블에는 존재하지 않습니다.

SELECT * FROM MEMBER WHERE TEAM_ID = 1; -- Team → Member 조회도 가능

이 차이 때문에 객체를 테이블에 맞추다 보면 실제로는 필요 없는 방향의 참조까지 양방향으로 만들게 되는 경우가 많습니다.

 

3. 그래프 탐색의 단절

객체는 참조를 계속 따라가며 자유롭게 탐색할 수 있습니다.

member.getTeam().getCompany().getAddress();

하지만 데이터베이스에서 가져온 객체는 처음 실행한 SQL의 범위 안에서만 탐색이 가능합니다. `SELECT * FROM MEMBER`로 조회한 객체는 `team`이 채워져 있다는 보장이 없습니다.

 

4. 동일성과 동등성

같은 row를 두 번 조회해도 매번 다른 객체 인스턴스가 반환될 수 있습니다.

Member m1 = memberRepository.findById(1L);
Member m2 = memberRepository.findById(1L);

m1 == m2 // 순수 JDBC라면 false일 수 있음

객체지향에서는 동일한 식별자를 가진 두 객체가 `==` 비교에서 다르게 취급될 수 있다는 점이 직관과 어긋납니다.

 

ORM으로 간극을 메우기

 

JPA나 Hibernate 같은 ORM(Object-Relational Mapping)* 은 위에서 살펴본 불일치를 상당 부분 자동으로 처리해 줍니다.

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}
Member member = new Member();
member.setName("Aaron");
member.setTeam(team);

entityManager.persist(member); // INSERT 쿼리를 직접 작성하지 않음

개발자는 SQL을 직접 작성하는 대신 객체를 중심으로 코드를 작성하고, ORM이 내부적으로 이를 SQL로 변환해 실행합니다. 동일성 문제도 영속성 컨텍스트**가 1차 캐시를 통해 같은 트랜잭션 안에서는 동일한 객체를 반환하도록 보장해 줍니다.

Member m1 = em.find(Member.class, 1L);
Member m2 = em.find(Member.class, 1L);

m1 == m2 // true, 같은 영속성 컨텍스트 안에서는 동일 객체 보장

 

마치며

임피던스 불일치는 없앨 수 없는 근본적인 구조 차이입니다. ORM은 이 불일치를 완전히 없애는 것이 아니라, 반복적이고 기계적인 매핑 작업을 대신 처리해 줌으로써 개발자가 객체지향 설계에 더 집중할 수 있게 도와주는 도구입니다. 다만 지연 로딩, N+1 문제처럼 ORM이 만들어내는 또 다른 종류의 트레이드오프도 함께 이해하고 있어야 실무에서 제대로 활용할 수 있습니다.

 

 

  • *ORM(Object-Relational Mapping): 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해 주는 기술 또는 프레임워크
  • **영속성 컨텍스트(Persistence Context): JPA가 엔티티를 영구 저장하는 환경으로, 엔티티를 관리하며 1차 캐시, 동일성 보장, 쓰기 지연 등의 기능을 제공하는 논리적 개념
728x90
300x250