Spring/강의

[📙 숙련 Spring] 3-1. JPA

가지코딩 2025. 5. 20. 23:40

📙 목차

  1. 패러다임 불일치(Paradigm Mismatch)
  2. JPA
  3. JPA 사용 이유
  4. 영속성 컨텍스트

1. 패러다임 불일치(Paradigm Mismatch)

패러다임 불일치(Paradigm Mismatch)

  • 객체 지향 프로그래밍(Object-Oriented Programming)과 관계형 데이터베이스(Relational Database)가 서로 다른 설계 철학과 구조를 가지고 있기 때문에 이 둘을 연결할 때 생기는 구조적·개념적 충돌을 의미한다.
  • 객체는 클래스와 참조, 캡슐화, 상속, 다형성 등 객체 지향 개념을 따르지만
  • RDB는 테이블, 외래 키, 정규화, SQL 등 관계형 모델을 기반으로 동작한다.
  • 이러한 차이로 인해, 객체를 RDB에 저장하거나 불러올 때 반복적인 SQL 작성, JOIN 처리, 객체 조립, 동일성 문제 등 다양한 어려움이 발생한다.

 

 

객체 vs 관계형 데이터베이스

구분 객체(Object) 관계형 DB(Relational DB)
구조 단위 클래스 (Class) 테이블 (Table)
데이터 단위 인스턴스 (Object) 레코드 (Row)
속성 표현 필드(Field), 참조형 포함 컬럼(Column), 기본형만
관계 표현 참조(Reference)로 표현 외래 키(Foreign Key)로 연결
상속 클래스 상속 지원 직접적인 상속 구조 없음
다형성 메서드 오버라이딩 등 지원 미지원 (테이블로 분리 필요)
데이터 탐색 객체 그래프 탐색 (객체 간 연결) JOIN 기반 탐색 (명시적 SQL 필요)
식별 방식 동일성 (==) or equals() 기본 키(Primary Key)로 식별
데이터 변경 객체 필드만 수정하면 됨 SQL로 명시적 갱신 필요

 

 

대표적인 패러다임 불일치 사례

구분 설명
상속 불일치 객체는 상속이 가능하지만, RDB는 상속 개념이 없음. 이를 테이블로 구현하려면 JOIN이나 단일 테이블 전략 등이 필요.
참조 vs 외래 키 객체는 필드에 다른 객체를 직접 참조하지만, RDB는 외래 키(FK)로 관계를 표현함. 이를 매핑하려면 객체 그래프 ↔ 외래 키 변환 로직이 필요.
동일성 문제 객체는 == 또는 equals()로 비교하지만, RDB는 기본 키(PK)로 식별. 식별 방식이 달라서 혼동 발생 가능.
네비게이션 방식 차이 객체는 필드를 따라 연결된 객체 탐색이 가능하지만, RDB는 명시적인 JOIN이 필요함. 개발자가 직접 SQL을 짜야 함.
데이터 갱신 방식 차이 객체는 필드만 수정하면 끝나지만, RDB는 변경 쿼리를 작성해야 함. 트랜잭션, 동기화 등의 문제가 따름.
컬렉션 처리 문제 객체는 리스트, 세트 등 컬렉션으로 연관 객체를 가질 수 있지만, RDB는 이를 표현하기 위해 추가 테이블이 필요함.

 

* 이런 문제를 해결하기 위해 등장한 것이 JPA (Java Persistence API) 같은 ORM(Object-Relational Mapping) 기술이다.


2. JPA

JPA (Java Persistence API)

  • 자바 객체와 관계형 데이터베이스를 자동으로 매핑해주는 ORM 기술 표준
  • 객체와 테이블 사이의 패러다임 불일치(Paradigm Mismatch) 문제를 해결하기 위해 자바 진영에서 만든 "표준 ORM API"이다.
  • 반복적인 SQL 작성 없이, 객체 중심의 비즈니스 로직 개발이 가능하도록 도와준다.
  • JPA는 인터페이스(표준)만 정의
    • 실제 구현체로는 Hibernate, EclipseLink, DataNucleus 등이 있다.
    • 우리가 보통 사용하는 @Entity, @Id, @OneToMany 같은 어노테이션이 JPA에 해당한다.

 

JPA 핵심 개념

개념 설명
Entity DB 테이블에 매핑되는 자바 클래스 (@Entity 사용)
EntityManager JPA의 핵심 인터페이스. 객체 저장, 조회, 삭제, 갱신 등을 담당
영속성(Persistence) 객체의 생명주기를 관리하고 DB와 동기화하는 개념
JPQL 객체지향 쿼리 언어. SQL과 유사하지만, 테이블이 아닌 객체(Entity)를 대상으로 한다
어노테이션 기반 매핑 @Table, @Column, @OneToMany, @JoinColumn 등을 이용해 객체와 테이블을 연결

 


3. JPA 사용 이유

JPA 사용 이유

  • 생산성 향상
  • 유지보수 용이
  • 패러다임 불일치 문제 해결
  • 성능 최적화

 

 

생산성 향상

  • 반복적인 SQL 없이 persist(), find() 등 메서드만으로 저장·조회·수정·삭제 가능
  • 객체처럼 다루기 때문에 직관적이고 코드가 간결하다.
jpa.persist(tutor); // 저장  
Tutor tutor = jpa.find(Tutor.class, tutorId); // 조회  
tutor.setName("수정 이름"); // 수정  
jpa.remove(tutor); // 삭제

 

 

유지보수 용이

  • 객체 필드가 변경되어도 SQL 수정 불필요 (JPA가 내부적으로 처리)
// 필드 추가 전
public class Tutor {
    private String id;
    private String name;
}

// 필드 추가 후
public class Tutor {
    private String id;
    private String name;
    private Integer age;
}

 

 

패러다임 불일치 문제 해결

  • 상속
    • 객체의 상속 구조를 관계형 테이블에 맞게 변환하여 저장
    • ex. 부모 클래스 Person과 자식 클래스 Tutor를 각각 테이블로 분리 저장하고, 조회 시 JOIN 처리
@Entity
public class Person {
    @Id
    private Long id;
    private String name;
}

@Entity
public class Tutor extends Person {
    private String subject;
}

 

  • 연관관계 매핑
    • 객체 간 참조 관계(@ManyToOne, @OneToMany)를 데이터베이스 외래키 관계로 자동 변환
    • 코드에서는 객체 참조처럼 다루고, JPA가 적절한 SQL로 변환해 처리
@Entity
public class Tutor {
    @ManyToOne
    private Company company;
}

 

  • 객체 그래프 탐색
    • 여러 객체가 연결된 상태를 그대로 탐색 가능
    • Tutor 객체에서 Company 객체로 자연스럽게 접근할 수 있고, 내부적으로 필요한 JOIN이나 추가 쿼리를 실행
Tutor tutor = jpa.find(Tutor.class, tutorId);
Company company = tutor.getCompany();  // SQL JOIN 없이 객체 참조처럼 사용 가능

 

  • 객체 비교
    • 같은 트랜잭션 내에서 같은 식별자를 가진 엔티티는 같은 객체 인스턴스로 관리
Tutor tutor1 = jpa.find(Tutor.class, tutorId);
Tutor tutor2 = jpa.find(Tutor.class, tutorId);

System.out.println(tutor1 == tutor2);  // true

 

 

성능 최적화

  • 1차 캐시
jpa.find(...); // 첫 호출 → DB 조회  
jpa.find(...); // 두 번째 → 캐시 조회 (SQL 실행 없음)

 

  • 쓰기 지연(Batching)
jpa.persist(...); // 쌓아두고  
transaction.commit(); // 한 번에 DB 전송 (네트워크 비용 ↓)

 

  • 지연 로딩 / 즉시 로딩
    • 지연 로딩: 필요한 시점에 조회 → 불필요한 통신 방지
    • 즉시 로딩: 한 번에 JOIN 조회 → 효율적인 데이터 접근

4. 영속성 컨텍스트

영속성 컨텍스트

  • JPA에서 엔티티 객체를 관리하는 일종의 메모리 공간
  • 데이터베이스와 애플리케이션 사이에서 엔티티의 상태를 관리하고, 데이터베이스 접근을 최적화하는 역할을 한다.

 

영속성 컨텍스트가 필요한 이유

  • 데이터베이스에 자주 접근하는 비용을 줄이기 위해
  • 동일한 엔티티에 대한 중복 조회를 방지하기 위해
  • 트랜잭션 단위로 엔티티 상태 변화를 관리하기 위해

 

동작 방식

 

  • 애플리케이션이 엔티티를 조회하면, JPA는 해당 엔티티를 영속성 컨텍스트에 저장한다.
  • 같은 트랜잭션 내에서 동일한 엔티티를 다시 조회할 때는 데이터베이스가 아닌 영속성 컨텍스트에 저장된 객체를 반환한다.
  • 엔티티의 상태가 변경되면, 영속성 컨텍스트가 그 변경 사항을 추적한다.
  • 트랜잭션이 커밋될 때, 영속성 컨텍스트는 변경된 내용을 데이터베이스에 반영한다.

 

 

핵심 기능

  • 동일성 보장 (Identity Guarantee)
    • 영속성 컨텍스트는 같은 엔티티에 대해 항상 같은 자바 객체 인스턴스를 반환한다.
    • 즉, 한 트랜잭션 내에서 동일한 엔티티 식별자(ID)를 가진 객체는 단 하나만 존재한다.
    • 이를 통해 객체 참조 비교(==)가 가능해지고, 데이터 일관성이 유지된다.
  • 쓰기 지연 (Write-Behind)
    • 엔티티의 변경 사항을 즉시 데이터베이스에 반영하지 않고, 트랜잭션이 커밋되거나 flush() 호출 시점에 한꺼번에 반영한다.
    • 이를 통해 여러 SQL 실행을 최소화하여 성능을 최적화한다.
  • 변경 감지 (Dirty Checking)
    • 영속성 컨텍스트는 트랜잭션 동안 엔티티의 상태 변화를 감지한다.
    • 객체의 필드 값이 변경되면, 그 변경 내용을 기억했다가 flush 시점에 업데이트 쿼리를 생성한다.
    • 개발자가 직접 update 쿼리를 작성하지 않아도 변경 사항이 자동으로 반영된다.
  • flush()
    • flush()는 영속성 컨텍스트에 저장된 변경 내용을 데이터베이스에 즉시 반영하는 동작이다.
    • 트랜잭션 커밋 시 자동으로 호출되지만, 필요에 따라 개발자가 직접 호출할 수도 있다.
    • 단, flush()는 변경 내용을 반영하지만 트랜잭션을 종료하지는 않는다.

 

엔티티 상태 관리

상태 설명
비영속 영속성 컨텍스트에 속하지 않은 상태
영속 영속성 컨텍스트에 관리되는 상태
준영속 영속성 컨텍스트에서 분리된 상태
삭제 삭제 예정 상태, 트랜잭션 커밋 시 삭제