자바 개발자라면 JPA를 왜 꼭 알아야 할까?

2026. 7. 10. 22:56Dev & Tech/Java

반응형

자바 개발자라면 JPA를 왜 꼭 알아야 할까?

스프링 부트로 프로젝트를 시작하면 거의 예외 없이 마주치는 이름이 있습니다. 바로 JPA(Java Persistence API)입니다. 채용 공고에도 빠지지 않고 등장하고, 실무에서도 당연하다는 듯 쓰이지만, 정작 "JPA가 정확히 뭔가요?"라고 물으면 설명이 막막해지는 경우가 많습니다.

이번 글에서는 JPA가 왜 등장했는지부터, 핵심 개념, 스프링 부트에서의 기본 사용법, 그리고 처음 배울 때 자주 헷갈리는 부분까지 최대한 쉬운 말로 차근차근 정리해보겠습니다.
목차
1. JPA란 무엇이고 왜 등장했을까
2. JPA의 핵심 개념 이해하기
3. 스프링 부트에서 JPA 기본 사용법
4. 처음 배울 때 자주 헷갈리는 부분과 학습 팁

#1. JPA란 무엇이고 왜 등장했을까
1) JDBC의 불편함에서 시작된 이야기
JPA를 이해하려면 먼저 JPA가 없던 시절 이야기를 알아야 합니다. 예전에는 자바에서 데이터베이스를 다루려면 JDBC(Java Database Connectivity)를 사용해 SQL을 직접 작성해야 했습니다.
// JDBC로 회원 정보를 조회하는 예시(단순화)
String sql = "SELECT * FROM member WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setLong(1, memberId);
ResultSet rs = pstmt.executeQuery();
// 이후 결과를 하나씩 꺼내 객체에 옮겨 담는 코드가 이어진다
문제는 회원, 주문, 상품처럼 다루는 대상이 늘어날수록 비슷한 SQL과 반복 코드가 계속 늘어난다는 점이었습니다. 테이블에 칼럼 하나만 추가돼도 관련된 SQL을 전부 찾아 고쳐야 하는 번거로움도 있었습니다.
(1) 반복되는 패턴, 그리고 실수의 위험
회원을 저장하는 코드, 조회하는 코드, 수정하는 코드가 프로젝트 곳곳에 비슷하게 반복되다 보면 오타나 실수가 생기기도 쉬워집니다. 특히 SQL 문자열은 컴파일 시점에 오류를 잡아주지 않기 때문에, 실행해보기 전까지는 문제를 알아채기 어렵다는 단점도 있었습니다.
· · · · ·
2) 객체와 관계형 데이터베이스의 동상이몽
자바는 객체지향 언어이고, 데이터베이스는 테이블(행과 열)로 데이터를 다룹니다. 이 둘은 데이터를 표현하는 방식 자체가 달라서, 객체를 테이블에 맞게 변환하고 다시 테이블 데이터를 객체로 조립하는 과정에서 많은 코드가 필요했습니다. 이런 간극을 흔히 '패러다임 불일치'라고 부릅니다.
(1) ORM이라는 해결책
이 문제를 풀기 위해 등장한 개념이 ORM(Object-Relational Mapping)입니다. 객체와 테이블을 미리 매핑해두면, 개발자는 SQL을 직접 작성하지 않고도 객체를 다루는 것만으로 데이터베이스 작업이 가능해집니다.
· · · · ·
3) JPA는 표준, Hibernate는 구현체
여기서 헷갈리기 쉬운 부분이 하나 있습니다. JPA는 자바 진영에서 정한 ORM 표준 명세(인터페이스 모음)일 뿐, 그 자체로 동작하는 프로그램이 아닙니다. 실제로 동작하려면 이 명세를 구현한 라이브러리가 필요한데, 가장 널리 쓰이는 구현체가 하이버네이트(Hibernate)입니다.
구분역할
JPAORM 표준 명세(인터페이스)
HibernateJPA 명세를 구현한 실제 프레임워크
Spring Data JPAJPA 사용을 더 편하게 만들어주는 스프링 모듈
비유하자면 JPA는 "이런 기능을 제공해야 한다"는 약속(인터페이스)이고, Hibernate는 그 약속을 실제로 지키는 구현체인 셈입니다.
※ 참고: Jakarta Persistence(구 JPA) 공식 명세 및 Hibernate 공식 문서 기준으로 정리한 일반적인 개념 설명입니다.

#2. JPA의 핵심 개념 이해하기
1) 엔티티(Entity)
JPA에서 엔티티는 데이터베이스 테이블과 매핑되는 자바 클래스를 말합니다. 클래스에 몇 가지 어노테이션만 붙이면, JPA가 이 클래스를 테이블과 자동으로 연결해줍니다.
@Entity // 이 클래스가 엔티티임을 선언
public class Member {
  @Id // 기본키(primary key) 지정
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String name;
  private String email;
}
· · · · ·
2) 영속성 컨텍스트와 EntityManager
JPA를 이해하는 데 가장 중요한 개념이 바로 영속성 컨텍스트(Persistence Context)입니다. 쉽게 말하면, 엔티티를 메모리에 보관하고 관리하는 1차 저장소라고 생각하면 됩니다. 그리고 이 영속성 컨텍스트를 실제로 다루는 관리자가 EntityManager입니다.
(1) 왜 이런 중간 저장소가 필요할까
영속성 컨텍스트가 있으면, 같은 엔티티를 여러 번 조회해도 매번 데이터베이스에 다시 접근하지 않고 메모리에 저장된 객체를 재사용할 수 있습니다. 또한 변경 사항을 데이터베이스에 반영하는 시점도 JPA가 알아서 관리해줍니다.
· · · · ·
3) 엔티티의 4가지 생명주기
엔티티는 상태에 따라 크게 4단계로 구분됩니다. 처음에는 다소 낯설 수 있지만, 하나씩 따라가 보면 어렵지 않습니다.
상태의미
비영속(new)객체만 생성, 아직 영속성 컨텍스트와 무관
영속(managed)영속성 컨텍스트가 관리하는 상태
준영속(detached)영속성 컨텍스트에서 분리된 상태
삭제(removed)삭제가 예약된 상태
Member member = new Member(); // ① 비영속 상태
member.setName("홍길동");

em.persist(member); // ② 영속 상태로 전환

em.detach(member); // ③ 준영속 상태로 전환

em.remove(member); // ④ 삭제 예약 상태
· · · · ·
4) 1차 캐시와 더티 체킹
영속성 컨텍스트의 대표적인 두 가지 기능이 1차 캐시더티 체킹(dirty checking)입니다.
(1) 1차 캐시란
영속 상태가 된 엔티티는 영속성 컨텍스트 내부에 캐시처럼 저장됩니다. 그래서 같은 ID로 다시 조회하면, 데이터베이스에 다시 쿼리를 보내지 않고 캐시에 있는 객체를 그대로 반환합니다.
(2) 더티 체킹이란
영속 상태의 엔티티 값을 변경하면, 별도로 저장 메서드를 호출하지 않아도 트랜잭션이 끝나는 시점에 변경 사항이 자동으로 데이터베이스에 반영됩니다. JPA가 처음 조회했을 때의 상태와 현재 상태를 비교해 변경분을 찾아내기 때문에 붙은 이름입니다.
※ 참고: Hibernate 공식 문서 및 JPA 명세 기준으로 정리한 개념입니다.
· · · · ·
5) 복잡한 조회는 JPQL로
기본 CRUD를 넘어서는 복잡한 조회가 필요할 때는 JPQL(Java Persistence Query Language)을 사용합니다. SQL과 비슷하게 생겼지만, 테이블이 아니라 엔티티(객체)를 대상으로 쿼리를 작성한다는 점이 다릅니다.
// JPQL 예시: 이름으로 회원을 조회
String jpql = "SELECT m FROM Member m WHERE m.name = :name";
List<Member> result = em.createQuery(jpql, Member.class)
  .setParameter("name", "홍길동")
  .getResultList();
FROM 뒤에 테이블 이름이 아니라 엔티티 클래스 이름(Member)이 온다는 점을 눈여겨보시면 좋습니다.

#3. 스프링 부트에서 JPA 기본 사용법
1) Spring Data JPA로 반복 코드 줄이기
순수 JPA만 사용해도 되지만, 스프링 부트 프로젝트에서는 보통 Spring Data JPA를 함께 사용합니다. 인터페이스 하나만 선언하면 기본적인 CRUD 메서드를 자동으로 만들어주기 때문입니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
  // 메서드 이름만으로 쿼리가 자동 생성된다
  Optional<Member> findByEmail(String email);
}
save(), findById(), findAll(), delete() 같은 기본 메서드는 별도로 구현하지 않아도 이미 제공됩니다. 메서드 이름 규칙에 맞춰 findByEmail처럼 새 메서드를 선언하면, Spring Data JPA가 이름을 분석해 알아서 쿼리를 만들어줍니다.
· · · · ·
2) 연관관계 매핑 기초
실무 데이터는 대부분 서로 연결돼 있습니다. 예를 들어 회원 한 명은 여러 개의 주문을 가질 수 있는데, 이런 관계를 JPA에서는 어노테이션으로 표현합니다.
@Entity
public class Order {
  @Id @GeneratedValue
  private Long id;

  @ManyToOne // 주문 여러 개가 회원 한 명에 연결
  @JoinColumn(name = "member_id")
  private Member member;
}
(1) 연관관계의 주인 개념
양방향 연관관계를 맺을 때는 '연관관계의 주인'이라는 개념이 등장합니다. 외래키를 가진 쪽(보통 @ManyToOne이 붙는 쪽)이 주인이 되어, 실제 데이터베이스 값 변경을 담당합니다. 처음에는 헷갈릴 수 있는 개념이니, 일단 "외래키가 있는 쪽이 주인"이라는 규칙만 기억해도 충분합니다.
· · · · ·
3) 트랜잭션 안에서 다루기
JPA의 영속성 컨텍스트는 기본적으로 트랜잭션과 함께 시작되고 종료됩니다. 스프링에서는 서비스 메서드에 @Transactional 어노테이션을 붙이는 것만으로 이 범위를 간단히 지정할 수 있습니다.
@Service
public class MemberService {
  @Transactional
  public void updateName(Long id, String newName) {
    Member member = memberRepository.findById(id).orElseThrow();
    member.setName(newName); // 더티 체킹으로 자동 반영
  }
}
※ 참고: Spring Data JPA 공식 문서 기준으로 정리한 기본 사용 예시입니다.

#4. 처음 배울 때 자주 헷갈리는 부분과 학습 팁
1) N+1 문제란 무엇일까
JPA를 배우다 보면 반드시 마주치는 용어가 N+1 문제입니다. 예를 들어 회원 목록을 조회할 때, 각 회원의 주문 정보까지 함께 필요하다면 회원 조회 쿼리 1번에 이어 회원 수만큼 추가 쿼리가 발생하는 현상을 말합니다.
(1) 왜 이런 일이 생길까
연관된 데이터를 필요할 때만 가져오는 지연 로딩(Lazy Loading) 방식 때문에 생기는 대표적인 부작용입니다. 처음에는 원인을 파악하기 어려워 많은 초보 개발자들이 이 문제로 애를 먹습니다. 해결 방법으로는 fetch join이나 별도 조회 쿼리를 활용하는 방법이 있는데, 이 부분은 JPA에 어느 정도 익숙해진 뒤 별도로 깊이 있게 학습하시길 권해드립니다.
· · · · ·
2) 즉시 로딩과 지연 로딩
연관된 엔티티를 언제 가져올지 결정하는 두 가지 전략입니다.
구분설명
즉시 로딩(EAGER)엔티티 조회 시 연관 데이터도 함께 조회
지연 로딩(LAZY)연관 데이터를 실제 사용하는 시점에 조회
실무에서는 예상치 못한 쿼리 폭증을 막기 위해 대부분의 연관관계를 지연 로딩으로 설정하는 것이 일반적인 권장 사항입니다.
· · · · ·
3) 처음 공부할 때 추천하는 순서
엔티티와 기본 CRUD 익히기 - @Entity, @Id 같은 기본 어노테이션부터 시작합니다.

영속성 컨텍스트 개념 이해하기 - 1차 캐시, 더티 체킹의 동작 원리를 파악합니다.

연관관계 매핑 연습하기 - @OneToMany, @ManyToOne을 활용한 간단한 예제를 직접 만들어봅니다.

N+1 문제와 성능 최적화 학습하기 - 어느 정도 익숙해진 뒤 fetch join, 페이징 등을 공부합니다.
(1) 처음부터 완벽히 이해하려 하지 않기
영속성 컨텍스트나 연관관계의 주인 같은 개념은 글로만 읽어서는 완전히 와닿지 않는 경우가 많습니다. 간단한 예제 프로젝트를 직접 만들어보면서 로그로 찍히는 쿼리를 눈으로 확인하다 보면, 훨씬 빠르게 감을 잡을 수 있습니다.
(2) 공식 문서와 로그를 친구로 삼기
막히는 부분이 생기면 블로그 글보다 스프링 부트 설정에서 SQL 로그를 켜고 직접 확인하는 습관을 들이는 것이 가장 빠른 학습법입니다. application.yml에 show-sql 옵션을 켜두면, 내가 작성한 코드가 실제로 어떤 쿼리로 변환되는지 눈으로 직접 확인할 수 있습니다.
※ 참고: 본 학습 순서는 일반적인 JPA 입문 커리큘럼을 참고해 정리한 권장 사항입니다.

마무리
JPA는 처음에는 낯선 용어투성이지만, 결국 핵심은 "SQL을 직접 쓰는 대신 객체를 다루는 것만으로 데이터베이스 작업을 하자"는 아이디어 하나로 요약됩니다. 영속성 컨텍스트, 1차 캐시, 더티 체킹 같은 개념도 이 아이디어를 실현하기 위한 장치라고 생각하면 훨씬 이해가 쉬워집니다.

처음부터 모든 개념을 완벽히 외우려 하지 마시고, 간단한 엔티티 하나를 직접 만들어 저장하고 조회해보는 것부터 시작해보시길 권해드립니다. 직접 코드를 돌려보고 로그에 찍히는 쿼리를 확인하는 과정에서, 오늘 정리한 개념들이 하나씩 실감 나게 다가올 것입니다.

JPA는 결국 많은 개발자들이 이미 거쳐간 학습 곡선입니다. 지금 낯설게 느껴지는 용어들도, 작은 프로젝트 하나를 완성해보고 나면 어느새 익숙해져 있을 것입니다.
긴 글 읽어주셔서 감사합니다.


끝.


반응형