-
[Data] JPA - 나오게 된 배경, 사용 이유Data 2026. 1. 3. 20:24
이번 포스팅에서는 Java의 표준 ORM 기술인 JPA에 대해서 살펴보겠습니다.
1. SQL 중심 개발의 문제점과 패러다임의 불일치
과거 자바 애플리케이션 개발의 핵심은 'SQL 매핑' 이었습니다. 비즈니스 요구사항을 분석하고 객체 지향적으로 설계를 하더라도, 결국 데이터를 영구 저장하기 위해 데이터베이스(RDB)에 맞춘 SQL을 작성해야 했기 때문입니다.
이 과정에서 우리는 크게 두 가지 문제에 직면합니다.
1) 무한 반복되는 CRUD와 SQL 의존성
Member라는 객체에 tel 필드를 하나 추가한다고 가정해 봅시다.
- Member 클래스 필드 추가
- INSERT 쿼리에 컬럼 추가
- SELECT 쿼리에 컬럼 추가
- UPDATE 쿼리에 컬럼 추가
- ResultSet 매핑 코드 수정
단순한 필드 하나 추가인데도 관련된 모든 코드를 수정해야 합니다. 이는 개발자가 비즈니스 로직보다 SQL 작성에 더 많은 에너지를 쏟게 만들며, '진정한 의미의 계층 분할'을 불가능하게 만듭니다. (물리적으로는 분리되어 있지만, 논리적으로는 엔티티와 SQL이 강결합 상태가 됨)
2) 패러다임의 불일치 (Paradigm Mismatch)
더 근본적인 문제는 객체 지향 프로그래밍(OOP)과 관계형 데이터베이스(RDB)가 지향하는 목적이 다르다는 점입니다.
우리는 무의식적으로 객체를 테이블에 맞추어 데이터를 저장하고 불러옵니다. 하지만 이 과정에서 객체 지향 언어가 가진 강력한 무기들을 포기하거나, 이를 구현하기 위해 엄청난 비용(복잡한 SQL 작성)을 치르게 됩니다. 구체적으로 어떤 문제가 발생하는지 살펴보겠습니다.
상속(Inheritance): 객체의 상속 vs DB의 슈퍼타입-서브타입
객체 지향 프로그래밍에서 '상속'은 자연스럽고 필수적인 개념입니다. 예를 들어 쇼핑몰의 상품(Item)을 상속받는 음반(Album), 영화(Movie), 책(Book) 클래스가 있다고 가정해 봅시다.
Java (객체): Album 객체를 저장하려면 단순히 컬렉션에 넣듯 저장하면 됩니다.
list.add(album); // 부모(Item)와 자식(Album)의 데이터가 함께 관리됨RDB (데이터베이스): DB에는 상속이라는 개념이 없습니다. 대신 '슈퍼타입-서브타입 모델'이라는 기법으로 흉내를 내야 하는데, 이를 위해서는 ITEM 테이블과 ALBUM 테이블 두 군데에 데이터를 나누어 저장해야 합니다.
- 저장(INSERT): 개발자는 Album 객체 하나를 저장하기 위해 두 번의 SQL을 작성해야 합니다.
- INSERT INTO ITEM ... (이름, 가격 등)
- INSERT INTO ALBUM ... (아티스트 등)
- 조회(SELECT): Album을 조회하려면 ITEM 테이블과 ALBUM 테이블을 JOIN하는 복잡한 SQL을 매번 작성해야 합니다. 또한, 조회한 결과를 각각의 객체 필드에 일일이 매핑해주는 RowMapper 작업도 필요합니다.
결국, DB 테이블 구조에 맞추기 위해 코드가 복잡해지고, 상속 기능을 활용하는 것을 꺼리게 됩니다.
연관관계(Association): 참조 vs 외래 키(FK)
객체와 테이블이 관계를 맺는 방식은 근본적으로 다릅니다.
- Java (객체): 참조(Reference)를 사용합니다. member.getTeam()을 통해 회원이 소속된 팀을 바로 알 수 있습니다. 참조는 기본적으로 단방향입니다.
- RDB (데이터베이스): 외래 키(FK)를 사용합니다. MEMBER 테이블에 TEAM_ID를 저장하여 관계를 맺습니다. JOIN을 사용하면 팀에서 회원을 찾을 수도, 회원에서 팀을 찾을 수도 있는 양방향 관계입니다.
테이블에 맞춰 객체를 설계하다 보면 우리는 다음과 같이 객체 지향적이지 않은 모델을 만들게 됩니다.
class Member { String id; Long teamId; // 객체(Team)가 아닌 외래 키(Long)를 그대로 필드로 가짐 String username; }이렇게 설계하면 데이터를 저장할 때는 편하지만(INSERT INTO MEMBER VALUES (..., teamId, ...)), 객체 지향적인 활용은 불가능해집니다.
만약 진짜 객체 지향적으로 Team team 필드를 넣게 되면 어떻게 될까요?
member.getTeam().getId(); // 참조를 통해 ID를 꺼내서 저장해야 함개발자는 객체와 테이블 사이에서 이 변환 작업을 끊임없이 수행해야 합니다.
3. 객체 그래프 탐색의 한계: 신뢰성 문제
객체 지향 시스템에서 객체들은 그물망처럼 연결되어 있는데, 이를 객체 그래프(Object Graph)라고 합니다. 이론적으로는 참조만 이어져 있다면 언제든지 탐색이 가능해야 합니다.
member.getOrder().getOrderItem().getItem().getCategory();하지만 SQL을 직접 다루는 환경에서는 '처음 실행하는 SQL'이 탐색 범위를 결정해 버립니다.
// DAO(Repository)에서 회원을 조회 Member member = memberDAO.find(memberId); // 과연 이 코드는 안전할까? member.getTeam(); // null? member.getOrder(); // null?개발자는 memberDAO.find() 메소드 내부를 열어서 어떤 SQL이 실행되었는지(JOIN을 어디까지 했는지) 직접 확인하기 전까지는 반환된 member 객체를 신뢰할 수 없습니다.
- SELECT * FROM MEMBER만 했다면? -> member.getTeam()은 null (NullPointerException 위험)
- SELECT * FROM MEMBER m JOIN TEAM t ...를 했다면? -> member.getTeam() 사용 가능
이처럼 비즈니스 로직(Service 계층)이 데이터 접근 로직(SQL)에 논리적으로 강하게 종속되는 현상이 발생하여, 진정한 의미의 계층 분할이 어려워집니다.
결국, 객체를 데이터베이스에 억지로 맞춰 넣으려다 보니 객체 지향의 장점을 모두 잃어버리는 모순이 발생합니다.
2. JPA(Java Persistence API)란 무엇인가?
이러한 패러다임의 불일치를 해결하기 위해 등장한 것이 바로 JPA입니다.
ORM (Object-Relational Mapping)
JPA를 이해하기 전, 먼저 ORM의 개념을 알아야 합니다. ORM은 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 기술입니다. 객체는 객체대로 설계하고, RDB는 RDB대로 설계하면, ORM 프레임워크가 중간에서 SQL을 생성하여 둘 사이의 차이를 메워줍니다.
JPA는 '표준 명세(Interface)'이다
많은 분들이 혼동하는 부분인데, JPA는 특정 라이브러리가 아니라 자바 진영의 ORM 기술 표준 명세(Interface)입니다. 즉, "이렇게 만들자"라고 정해놓은 명세서일 뿐입니다.
- JPA (Interface): EntityManager, EntityTransaction 등 표준 인터페이스 모음.
- Hibernate (Implementation): JPA 인터페이스를 실제로 구현한 가장 대표적인 라이브러리.
우리는 JPA라는 표준 인터페이스를 통해 개발하지만, 실제로 내부에서는 Hibernate와 같은 구현체가 동작하여 DB와 통신하게 됩니다.
3. 왜 JPA를 사용해야 하는가?
실무에서 JPA를 사용하는 명확한 이유가 있습니다.
1) 생산성 (Productivity)
가장 강력한 장점입니다. 더 이상 지루한 CRUD SQL을 짤 필요가 없습니다. 자바 컬렉션에 객체를 넣듯이 JPA에게 객체를 저장하라고 명령만 하면 됩니다.
- 저장: jpa.persist(member)
- 조회: Member member = jpa.find(memberId)
- 수정: member.setName("변경할 이름") (별도의 update 메소드 호출 불필요)
- 삭제: jpa.remove(member)
2) 유지보수성 (Maintenance)
앞서 언급한 필드 추가 상황을 떠올려 보세요. JPA를 사용하면 엔티티 클래스에 필드만 추가하면 끝입니다. 관련된 INSERT, UPDATE, SELECT SQL은 JPA가 알아서 처리해줍니다.
3) 패러다임 불일치 해결
JPA는 객체 지향의 장점을 살릴 수 있도록 다양한 기능을 지원합니다.
- 상속 매핑: 객체의 상속 구조를 DB 테이블 구조에 맞춰 자동으로 처리해줍니다.
- 지연 로딩 (Lazy Loading): 객체 그래프 탐색 시, 실제 객체를 사용하는 시점에 쿼리를 날려 데이터를 가져옵니다. 이를 통해 신뢰할 수 있는 엔티티 계층을 만들 수 있습니다.
4) 성능 최적화 기능
"JPA는 SQL을 직접 짜는 것보다 느리지 않나?"라는 오해를 많이 받습니다. 하지만 JPA는 애플리케이션과 DB 사이의 계층으로서 다양한 성능 최적화 기능을 제공합니다.
- 1차 캐시: 같은 트랜잭션 안에서는 같은 객체를 조회할 때 DB를 거치지 않고 메모리에서 반환합니다.
- 트랜잭션을 지원하는 쓰기 지연 (Transactional Write-behind): 트랜잭션을 커밋하기 전까지 INSERT SQL을 모았다가 한 번에 전송하여 네트워크 통신 비용을 줄입니다.
'Data' 카테고리의 다른 글
[JPA] Spring Data JPA의 핵심 개념 (1) 2026.01.24 [JPA] 기본적인 Spring Data Jpa 활용 (0) 2026.01.24 PL/SQL 실습 (2) 2025.05.01 PL/SQL이란? 오라클의 절차형 SQL 언어 정리 (0) 2025.05.01 데이터베이스 종류 (3) 2025.03.10