[Java] JPA란?

임재영·2025년 6월 4일
post-thumbnail

JPA란?


"Java Persistence API"의 준말로, Java 진영에서 ORM 기술 표준으로 사용되는 인터페이스의 모음이다.

먼저 JPA에 대해 알기 위해선, ORM이 무엇인지부터 살펴봐야 한다.



ORM (Object-Relational Mapping)


객체(Object)와, 관계형 데이터베이스(Relational Database)간의 불일치를 해결해주는 기술이다.

자바에서는 클래스로 객체를 만들고, 데이터베이스는 테이블로 데이터를 저장하는데, 이 둘은 구조와 방식이 다르다. 여기서 ORM은 둘 사이의 간극을 메워주는 매핑(연결)을 해주는 기술이다.

즉, 어플리케이션의 클래스(객체)와 관계형 데이터베이스의 테이블을 매핑하여, SQL을 직접 작성하지 않고도 개발 언어로 데이터베이스를 조작할 수 있게 해주는 기술이다.

기술적으로는 어플리케이션의 객체를 관계형 DB의 테이블에 자동으로 영속화(Persist)해주는 것이라고 보면 된다.


👉 여기서 영속성(Persistence)란?

데이터가 휘발되지 않고, 프로그램이 종료되어도 계속 저장되어 있는 상태를 말한다.
즉, 객체가 데이터베이스에 저장되어 지속적으로 관리되는 상태이다.

JPA에서는 객체를 단순히 new해서 사용하는 게 아니라, 특정 시점에 EntityManager에 의해 "영속 상태"로 전환시켜야 DB와 동기화할 수 있다.

JPA에서의 영속성

JPA에서는 객체(Entity)가 아래 네 가지 상태 중 하나에 있을 수 있다.

  • 비영속(Transient)
    아직 EntityManager에 저장되지 않은 상태(DB와 무관함)
  • 영속(Persistent)
    EntityManager가 관리하는 상태. DB와 동기화 대상
  • 준영속(Detached)
    한 번 영속되었지만, EntityManager의 관리에서 분리된 상태
  • 삭제(Removed)
    삭제가 예약된 상태. flush할 때 DB에서 삭제됨
    • flush() :
      JPA의 영속성 컨텍스트에서 변경된 내용을 DB에 반영(동기화)하는 동작을 말함.
      쉽게 말해, 영속성 컨텍스트에 있는 객체의 변경사항을 SQL로 바꿔서 DB에 보내는 작업.

영속성 컨텍스트(Persistence Context)

JPA에서 영속성을 관리하는 공간이 바로 영속성 컨텍스트다.

  • 일종의 1차 캐시로, 여기에 저장된 객체는 DB가 아니라 메모리 상에서 관리됨
  • 같은 트랜잭션 내에서 동일한 엔티티는 항상 같은 인스턴스로 보장됨
  • 변경 감지(Dirty Checking)를 통해 값이 바뀌면 자동으로 update SQL 수행

예시 코드

EntityManager em = ...;

Member member = new Member();  // 비영속
member.setName("Jaeyeong");

em.persist(member);  // 영속 상태 전환
  • persist()를 호출한 이후부터, member객체는 영속성 컨텍스트에 저장됨.
  • 이후 name이 변경되며, 트랜잭션 커밋 시 자동으로 update 문이 실행됨.



ORM의 장단점

✅장점

1. 비즈니스 로직에 집중 가능

  • SQL을 직접 작성하지 않고, 객체 중심으로 DB를 다룰 수 있다.
  • 내부적으로는 ORM이 SQL을 자동 생성하므로, 개발자는 객체 모델 설계와 로직 구현에만 집중할 수 있다.

2. 코드의 가독성과 생산성 향상

  • 반복적인 쿼리 및 결과 매핑 코드가 줄어들어, 더 깔끔하고 명확한 코드 작성이 가능하다.
  • CRUD 기능이 추상화되어 개발 속도가 빨라진다.

3. 객체지향적인 설계 유지

  • 데이터 접근도 객체 단위로 처리할 수 있어, 객체지향 패러다임에 맞는 설계를 유지할 수 있다.
  • 도메인 주도 설계(DDD)와도 잘 어울린다.

4. DB 구조에 대한 의존도 감소

  • 매핑 정보가 클래스에 정의되어 있어 ERD나 DB 스키마에 대한 의존이 줄어든다.
  • 유지보수 및 리팩터링이 용이하다.

5. DBMS 교체에 유연

  • 대부분의 ORM은 DBMS 독립적으로 동작하므로, MySQL → PostgreSQL 등 DB 변경 시에도 쿼리 수정을 최소화할 수 있다.

❎단점

1. 설계 실수 시 성능 저하

  • 잘못 설계된 엔티티 관계나 과도한 추상화는 성능 문제를 유발할 수 있다.
  • 특히 대규모 시스템에서는 더 민감하다.

2. 복잡한 쿼리 한계

  • 복잡한 조인, 성능 튜닝이 필요한 쿼리는 ORM만으로 처리하기 어렵다.
  • 결국 SQL(Native Query)을 직접 작성해야 하는 상황이 발생할 수 있다.

3. 학습 비용이 높음

  • 영속성 컨텍스트, 상태 전이, 연관관계 매핑 등 개념이 많아 학습 곡선이 가파르다.
  • 초보 개발자에겐 진입 장벽이 될 수 있다.



자바의 ORM 표준 : JPA


자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스.

인터페이스이기 때문에, 이를 사용하기 위해서는 구현체가 필요하고, JPA를 구현한 대표적인 프레임워크로는 Hibernate, EclipseLink, DataNucleus 등이 있으며, 이 중 Hibernate가 가장 널리 사용된다.


📌 Hibernate

자바 기반의 가장 대표적인 오픈소스 ORM 프레임워크.
JPA의 구현체 중 하나다.

1. JDBC 위에 구축된 고수준 ORM 프레임워크

  • Hibernate 내부에서는 여전히 JDBC API가 사용된다.
  • 하지만 개발자가 직접 SQL을 작성하지 않고도 객체 중심으로 DB에 접근할 수 있게 도와준다.
  • 즉, SQL 대신 자바 코드로 DB 조작이 가능하게 해주는 프레임워크이다.

2. HQL(Jibernate Query Language) 지원

  • Hibernate는 HQL이라는 자체 쿼리 언어를 제공한다.
  • HQL은 SQL과 문법이 유사하지만, 객체를 기준으로 쿼리를 작성한다.
  • 예: select m from Member m where m m.age > 20
    (SQL이 아니라 자바 클래스 Member 기준)

3. 완전한 객체지향 쿼리 언어

  • HQL은 자바 객체를 직접 다룰 수 있어, 상속, 다형성, 연관관계(Association) 등 객체지향 개념을 쿼리에서 그대로 사용할 수 있다.
  • SQL은 테이블과 컬럼을 대상으로 하지만, HQL은 클래스와 필드를 대상으로 한다.

4. 쿼리 결과로 객체 반환

  • HQL로 쿼리를 실행하면 결과는 테이블 행(row)이 아니라 자바 객체(Entity)로 반환된다.
  • 이는 개발자가 결과를 DTO나 Map으로 가공하는 작업 없이 직접 객체 필드에 접근할 수 있다는 장점을 가진다.

5. 고급 기능 제공

  • HQL은 SQL에서 제공하지 않는 기능도 갖고 있다. 예를 들어,
    • 페이지네이션(Pagenation): setFirstResult, setMaxResults를 통해 쉽게 구현 가능.
    • 동적 프로파일링(Dynamic Profiling): 실행 계획이나 파라미터를 유연하게 관리 가능.

6. 조인(join)에 유연

  • HQL에서는 명시적인 JOIN 문 없이도 연관 관계에 있는 객체를 쉽게 조회할 수 있다.
  • 이는 JPA의 객체 연관 매핑(연관관계 매핑)과 잘 어울려, 복잡한 조인 없이 객체 탐색 방식으로 데이터 조회가 가능하다.



☝️코드로 쉽게 이해하는 JPA 동작 과정


1️⃣ Entity 클래스 정의

  • DB 테이블과 매핑되는 자바 클래스(Entity)를 생성.
@Entity
public class Member {
    @Id
    private Long id;
    private String name;
}

🔸 이 클래스는 member라는 테이블과 매핑됨
🔸 각 필드는 컬럼과 매핑됨

2️⃣ EntityManager 생성

  • JPA의 핵심 객체인 EntityManager를 사용해서 DB 작업을 수행.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myUnit");
EntityManager em = emf.createEntityManager();

🔸 EntityManager는 엔티티(객체)를 관리하고,
🔸 DB와 통신할 때 필요한 내부 기능을 제공함

3️⃣ 트랜잭션 시작

  • 대부분의 JPA 작업은 트랜잭션 안에서 진행되어야 함.
EntityTransaction tx = em.getTransaction();
tx.begin();  // 트랜잭션 시작

4️⃣ 엔티티 저장 (persist)

Member m = new Member();
m.setId(1L);
m.setName("Jaeyoung");

em.persist(m);  // 영속성 컨텍스트에 저장됨

🔸 이 시점에는 DB에 INSERT 쿼리가 실행되지 않음
🔸 m 객체는 영속 상태가 되어 영속성 컨텍스트에 저장됨 (1차 캐시에 들어감)

5️⃣ 변경 감지(Dirty Checking)

m.setName("Jaeyoung Yim");  // 값을 수정

🔸 JPA는 트랜잭션이 커밋되기 전까지 객체의 변화를 감지함
🔸 변경된 필드가 있으면 → UPDATE 쿼리를 자동 생성

6️⃣ flush → commit

tx.commit();  // 트랜잭션 커밋

🔸 flush()가 자동 호출되며 → 변경 사항이 SQL로 변환되어 DB에 반영됨
🔸 INSERT, UPDATE, DELETE 실행됨

  • 이후 commit()으로 변경사항이 DB에 확정 저장됨

7️⃣ EntityManager 종료

em.close();
emf.close();

🔸 영속성 컨텍스트 종료, 연결 정리



✌️내부 동작으로 보는 JPA 동작 과정


JPA 동작 구조

  • 개발자는 자바 객체(Entity)만 조작하고, 내부적인 SQL 처리 및 DB 통신은 JPA가 자동으로 수행한다.
  • JPA는 내부적으로 JDBC API를 사용해 SQL을 생성하고 실행하며, 개발자는 SQL을 직접 작성할 필요가 없다.
  • 따라서, 개발자는 객체 중심의 코드 작성만으로도 데이터베이스와 통신할 수 있다.
  • 객체와 테이블 사이의 구조적 차이로 인한 패러다임 불일치(객체-관계 불일치, O/R Impedance Mismatch) 문제를 JPA가 자동으로 해결해준다.

저장 과정

-MemberDAO에서 객체를 저장하고 싶을 때, 개발자는 JPAMember 객체를 넘기면 아래와 같이 동작한다.

  • 4️⃣persist() 호출
    → 5️⃣ JPA가 엔티티를 분석하고
    INSERT SQL을 준비함 (아직 실행 X)

  • 6️⃣ 트랜잭션 커밋 시점에 flush() 자동 호출
    → JPA는 JDBC API를 사용해 SQL을 DB에 전송

  • DB에 레코드가 저장되고, 성공 여부를 응답받음


조회 과정

  • Member 객체를 조회하고 싶을 때, 개발자는 member의 PK 값을 JPA에 넘긴다.

  • 4️⃣find() 호출
    JPASELECT SQL을 생성

  • 6️⃣ JDBC API를 통해 SQL을 DB에 전송

  • DB에서 결과(ResultSet)를 받아
    Member 객체에 매핑

  • 매핑된 객체를 반환



JPA를 사용하는 이유


생산성

  • JPA는 Java 컬렉션에 객체를 넣고 빼듯이 데이터를 다룰 수 있게 해줌.

  • 반복적인 SQL 작성과 JDBC 코드 사용 없이, 객체만 조작하면 됨.

    // 객체 저장
    em.persist(member);
    
    // 객체 조회
    Member member = em.find(Member.class, memberId);
    
    // 객체 수정
    member.setName("홍길동");
    
    // 객체 삭제
    em.remove(member);
  • INSERT, UPDATE 같은 SQL 문은 JPA가 자동 생성.

  • CREATE TABLE 같은 DDL 문도 엔티티 기준으로 자동 생성 가능.

  • 개발은 객체 중심으로, DB 설계 중심에서 객체 설계 중심으로 패러다임 전환.

유지보수 용이

  • 기존 방식 : 테이블에 필드 하나 추가할 때마다 관련된 모든 SQL을 수정해야 함.
  • JPA 방식 : Entity 클래스에 필드만 추가하면 나머지는 JPA가 자동 처리.
  • 결과: SQL과 JDBC 관련 코드의 유지보수 부담이 크게 줄어듦.

객체-관계 패러다임 불일치 해결

  • 자바 객체와 RDB 테이블은 구조가 다름 (ex. 상속, 참조 등)
  • JPA는 다음과 같은 문제들을 자연스럽게 해결해줌:
    • 객체 상속 구조 매핑
    • 연관관계(Foreign Key → 객체 참조)
    • 객체 간 탐색 (객체 그래프)
    • 객체 비교 (equals/hashCode)

성능 최적화

  • JPA는 성능 최적화를 위한 다양한 기능 제공함.
    • 1차 캐시
    • 지연 로딩(Lazy Loading)
    • 변경 감지(Dirty Checking)
    • 배치 처리(Batch Insert/Update)
    • 페이징 처리

→ 애플리케이션과 DB 사이에서 중간 최적화 레이어 역할

데이터 접근 추상화 + DB 벤더 독립성

  • 개발자가 사용하는 API는 JPA로 고정되어 있고,
  • 하위 데이터베이스(MySQL, PostgreSQL 등)는 교체 가능

→ DBMS에 종속되지 않는 유연한 구조



Spring Data JPA


Spring에서 제공하는 데이터 접근 추상화 모듈로, JPA를 더 쉽고 편리하게 사용할 수 있도록 도와주는 프레임워크.

기존에 JPA를 사용하려면 EntityManager를 직접 주입받고, persist(), find() 등을 호출해야 했다.

Spring Data JPA는 이를 더 추상화한 Repository 인터페이스를 제공한다.
EntityManager를 직접 다루지 않아도, 필요한 메서드를 인터페이스로 선언만 하면 된다.

사용자가 Repository 인터페이스를 정의하고, 정해진 메서드 이름 규칙에 따라 메서드를 작성하면 Spring이 자동으로 해당 쿼리를 유추하여 실행하는 구현체를 만들어 스프링 빈으로 등록해준다.


Hibernate와 Spring Data JPA

HibernateJPA를 구현한 라이브러리이고,
Spring Data JPAJPA에 대한 데이터 접근을 추상화한 도구이다.

따라서 Spring Data JPA는 내부적으로 Hibernate와 같은 JPA 구현체를 필요로 한다.

즉, Spring Data JPA는 Hibernate 위에서 동작하며,
JPA를 사용하는 코드를 더욱 간결하고 선언적으로 작성할 수 있도록 도와준다.

이로써 개발자가 직접 Hibernate에 의존하지 않아도, Spring Data JPA가 내부적으로 Hibernate를 JPA 구현체로 활용해 동작한다.




[참고]
https://dev-coco.tistory.com/74
https://dbjh.tistory.com/77
https://phantom.tistory.com/58
https://chaeyami.tistory.com/256
https://ultrakain.gitbooks.io/jpa/content/chapter1/chapter1.3.html

0개의 댓글