[JPA 시리즈] JPA는 왜 나왔을까? — MyBatis를 겪으며 느낀 객체와 RDB의 간극 파헤치기
🧱 JPA는 왜 나왔을까? — MyBatis를 겪으며 느낀 객체와 SQL의 간극 파헤치기
1. 직접 경험한 MyBatis의 불편함
MyBatis로 프로젝트를 하며 가장 먼저 겪은 문제는 도메인 변경에 따른 코드 수정의 불편함이었습니다.
예를 들어, 제가 맡은 도메인이 팀원의 도메인과 연관되어 있었을 때, 팀원이 도메인을 수정하면 저도 SQL을 포함한 여러 코드를 변경해야 했습니다. 이는 단순히 협업 이슈가 아니라, DB 설계와 애플리케이션이 너무 밀접하게 엮여 있다는 근본적인 문제였습니다.
이 경험을 통해 “MyBatis는 DB 종속적이다”라는 말을 몸소 이해하게 되었습니다.
2. 도메인 중심이 되기 어려운 이유
MyBatis에서는 각 도메인마다 직접 CRUD SQL을 작성해야 합니다.
도메인에 필드 하나만 추가해도, 관련된 SQL마다 일일이 컬럼을 수정해야 합니다.
이 과정은 단순 반복작업이며, 개발자가 비즈니스 로직에 집중하지 못하게 만듭니다.
SQL 변경 → Mapper 변경 → Service/Controller 수정 → 테스트
이러한 긴 수정 루트를 따라야 합니다.
이런 구조는 DB가 변경되면 애플리케이션도 바뀌는 구조, 즉 계층이 분리되지 않은 아키텍처로 이어집니다.
3. 패러다임 불일치란?
- DB는 데이터를 정규화하고 관계로 표현하는 관계형 모델입니다.
- Java는 캡슐화, 상속, 다형성 등을 갖춘 객체지향 모델입니다.
이 두 모델 사이에는 개념적 차이가 존재하며, 이를 "패러다임의 불일치(Paradigm Mismatch)"
라고 부릅니다.
객체는 상태(속성)와 행동(메서드)을 갖지만, DB는 상태만 저장합니다.
또한, 객체 간의 참조 관계는 DB에서는 외래키로 간접 표현됩니다.
이 불일치를 극복하려면, 개발자가 각 상황에 맞는 매핑 로직을 직접 구현해야 합니다.
4. 상속이 문제될 때
Java에서는 상속이 자연스러운 구조이지만, 관계형 DB에는 상속 개념이 없습니다.
최대한 모델링을 한다 해도 슈퍼타입/서브타입 구조로 분리할 수밖에 없고, SQL도 두 테이블로 나누어 insert/update해야 합니다.
상속 하나 하려는데 insert 두 번 쓰고 조인도 해야 하는 상황이 발생합니다.
5. 연관관계도 까다롭습니다
Java에서는 객체 간 참조를 통해 자유롭게 관계를 표현합니다.
하지만 DB에서는 참조 대신 외래키(FK)
를 통해 관계를 맺습니다.
이를 코드로 매핑하려면, 참조형 필드 대신 id만 따로 받아서 DTO로 변환하는 작업이 필요합니다.
이는 곧 객체지향적이지 않은 코드가 된다는 의미입니다.
6. 객체 그래프 탐색의 제약
Java에서는 참조를 타고 객체 그래프를 따라가는 것이 자연스럽습니다.
하지만 SQL 기반 시스템에서는 처음에 작성된 SQL이 어디까지 join할지 미리 정해져야 합니다.
이는 SQL에 따라 객체 탐색이 제한되는 구조로, 객체지향적 개발에 방해가 됩니다.
7. 비교도 문제입니다
Java에서는 ==
으로 동일성(주소), equals()
로 동등성(내용)을 비교합니다.
하지만 같은 데이터를 조회했더라도, SQL 기반에서는 매번 다른 객체 인스턴스로 들어오기 때문에
동일성 비교는 실패하게 됩니다.
이로 인해 컬렉션 비교나 캐싱 등에서도 문제가 발생할 수 있습니다.
8. 그래서 어떻게 해결할 수 있을까? (자바 진영에서의 해결책: JPA)
이러한 문제를 해결하기 위해 자바 진영에서는 JPA, Hibernate 같은 ORM(Object-Relational Mapping) 기술이 등장했습니다.
상속의 경우
-
JPA는 상속 구조를 처리할 수 있도록 다양한 전략을 제공합니다.
→
@Inheritance(strategy=...)
-
부모, 자식 테이블을 조인하여 자동으로 객체를 생성해줍니다.
연관관계의 경우
- 객체의 참조는 자동으로 외래키로 변환되어 저장됩니다.
-
연관 객체를 필드로 선언만 하면 됩니다.
→
@ManyToOne
,@OneToMany
객체 그래프 탐색
- 필요한 시점에만 SQL을 실행하는
지연 로딩(Lazy Loading)
을 제공합니다. - 예:
order.getMember().getDelivery()
호출 시, 그때SELECT
를 실행합니다.
비교
-
JPA는 동일 트랜잭션 내에서는 동일한 객체를 보장합니다.
→ 1차 캐시(영속성 컨텍스트)를 사용해 중복 로딩을 방지합니다.
정리
MyBatis에서 겪은 어려움은 단순히 ‘불편하다’가 아니라,
객체지향적인 개발이 방해받고 있다는 신호였습니다.
JPA는 이러한 문제를 근본적으로 해결하려는 시도이며,
도메인 중심 개발을 가능하게 하는 기반 기술입니다.