본문 바로가기

MySql

[JDBC] JPA(Java Persistence API) 왜 사용할까?

 


 

개요

JDBC를 더 쉽게 사용하기 위한 JDBC Template은 Query의 결과를 객체로 변환해주는 Mapper를 사용할 수 있습니다. 이 Mapper는 DB 의존성 및 중복 쿼리 문제점이 있습니다. 또한 JDBC Template의 다음과 같은 문제점이 있습니다.

문제 설명 해결 방법
상속의 문제 RDB는 객체의 상속 관계를 표현할 수 없습니다. @OneToMany, @ManyToOne
관계 문제 RDB는 객체의 참조 관계 방향을 표현할 수 없습니다. @JoinColumn, @MappedBy
탐색 문제 RDB는 객체처럼 참조를 따라 순차적으로 탐색할 수 없습니다. @FetchType, fetchJoin()
밀도 문제 RDB는 객체처럼 복잡하고 큰 구조의 맴버 객체를 필드에 직접 가질 수 없습니다. @embedded
식별성 문제 RDB는 객체처럼 hashCode나 주솟값이 아닌 오직 기본키(PK)로만 데이터를 식별합니다. @Id, @GeneratedValue

 

💡이러한 문제점을 해결하기 위해 JPA가 등장하였습니다.

 


 

JPA(Java Persistence API)

정의

자바에서 ORM(Object-Relational Mapping)을 표준화한 인터페이스(명세)입니다. 즉, 자바 객체와 데이터베이스 테이블 간의 매핑을 쉽게 하기 위한 표준 ORM API입니다.

 

더보기

ORM(Object-Relational Mapping)

객체지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 테이블을 매핑(mapping)해주는 기술입니다.  쉽게 말해서 자바같은 프로그래밍 언어로 DB를 직접 다루게 해주는 도구입니다.

 

주요 목적

  1. SQL을 직접 쓰지 않고도 DB 접근
  2. 객체 중심 프로그래밍으로 DB 제어
  3. 영속성 컨텍스트, 더티 체킹, 1차 캐시 등 고급 기능 자동 처리
  4. 다양한 구현체에 대한 표준화된 추상 인터페이스 제공

 

 

핵심 개념

1. Entity

2. Field

3. Persistence Context(영속성 컨텍스트)

4. Repository

 


 

Entity

정의

데이터베이스 테이블과 매핑되는 자바 클래스입니다. 각각의 객체 인스턴스는 테이블의 한 행(Row)을 의미합니다.

 

 

예시

(테이블)

id name age
1 Alice 23
2 Bob 30
  • 각 컬럼(Column)은 자바 클래스의 Field 입니다.

 

(클래스)

import jakarta.persistence.*;

@Entity
@Table(name = "user")
public class User {

    @Id // 기본키 지정
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private int age;

    // 기본 생성자 필수
    public User() {}

    // 생성자, getter, setter 등 생략 가능
}
  • 이 클래스는 `user` 테이블과 연결되고, 객체 하나가 테이블의 한 줄을 나타냅니다.
  • 각 Field는 테이블의 컬럼(Column)을 의미합니다.

 

 

구성 요소

구성 요소 설명
`@Entity` 이 클래스가 엔티티임을 명시합니다.
`@Table` 매핑할 테이블 이름 지정 (생략 가능)
`@Id` 기본 키 (Primary Key) 지정
`@GeneratedValue` 기본 키 생성 방법을 지정
`@Column` (선택) 컬럼 세부 정보 지정, 클래스에 `@Entity`가 붙어있으면 자동으로 필드에 붙습니다.
`@Enumerated` Enum 매핑 용도로 사용. `@Enumerated(EnumType.STRING) 권장
`@Embeddable` 복합 값 객체로 사용할 클래스 지정
`@Embedded` 복합 값 객체 적용할 필드 지정
`@AttributeOverrides` 복합 값 객체 여러개 지정
`@AttributeOverride` 복합 값 객체 필드명 선언
`@Transient` 컬럼으로 맵핑하고 싶지 않은 맴버 변수에 지정

 

 

⭐ 엔티티를 지정해주는 이유

엔티티는 단순한 데이터 구조체가 아닙니다. JPA 같은 ORM 프레임워크가 엔티티를 통해 다음을 해줍니다.

  • SQL 없이 DB에 저장, 수정, 조회, 삭제하기 위해
  • 1차 캐시, 더티 체킹, 연관관계 관리 등 고급 기능을 사용하기 위해
  • 트랜잭션과 영속성 컨텍스트와 연결하기 위해

 


 

Persistence Context(영속성 컨텍스트)

정의

Entity 객체들을 보관하고 관리하는 JPA 내부의 메모리(캐시) 공간입니다.

 

 

역할

기능 설명
1차 캐시 동일한 엔티티를 또 조회하면 DB를 조회하지 않고 캐시에서 반환합니다.
변경 감지(Dirty Checking) 트랜잭션 내에서 값 변경을 추적하고, 자동으로 UPDATE 쿼리를 생성합니다.
쓰기 지연(Write Behind) INSERT, UPDATE, DELETE가 즉시 DB에 반영되지 않고 트랜잭션 종료 시 일괄 반영됩니다.
고유성 보장 같은 엔티티(PK 기준)는 항상 동일한 객체로 관리됩니다.
지연 로딩 지원 연관된 엔티티를 실제로 필요할 때 로딩

 

 

목적

1. 성능 최적화

2. 객체 중심 DB 작업을 지원합니다.

 

더보기

영속성

  • 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 의미합니다.
  • 영속성을 갖지 않으면 데이터는 메모리에서만 존재하게 되고 프로그램이 종료되면 해당 데이터는 모두 사라지게 됩니다.
  • 그래서 데이터를 파일이나 DB에 영구 저장함으로써 데이터에 영속성을 부여합니다.

 

 

데이터 연동 구조

JPA 이전 - DB 데이터 연동 구조
JPA - DB 데이터 연동 구조

  • 프로그램과 DB 사이에 `영속 컨텍스트`가 추가되었습니다.
  • 프로그램과 영속 컨텍스트 사이는 `EntityManager`를 통해 관리됩니다.
  • `EntityManager`가 객체의 영속성 상태(4가지)를 변경해줍니다.

 

영속성 4가지 상태

상태 설명
비영속(new/transient)  엔티티 객체가 만들어져서 아직 저장되지 않은 상태로, 영속성 컨텍스트와 전혀 관계 없는 상태
영속(managed) 엔티티가 영속성 컨텍스트에 저장되어, 영속성 컨텍스트가 관리할 수 있는 상태
준영속(detached) 엔티티가 영속성 컨텍스트에 저장되어 있다가 분리된 상태로, 영속성 컨텍스트가 더 이상 관리하지 않는 상태
삭제(removed) 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제하겠다고 표시한 상태
  • `Entity Manager`를 통해서 객체의 영속성 상태를 변경합니다.

 

 

예제 코드

Item item = new Item();		// 1
item.setItemNm("테스트 상품");	

EntityManager em = entityManagerFactory.createEntityManager();	// 2
EntityTransaction transaction = em.getTransaction();            // 3
	
transaction.begin();		
em.persist(item);       // 4
em.flush(item).         // 4-1 (DB에 SQL 보내기/commit시 자동수행되어 생략 가능함)
transaction.commit();   // 5

em.close();             // 6

1️⃣  영속성 컨텍스트에 담을 상품 엔티티 생성
2️⃣  엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
3️⃣  데이터 변경 시 무결성을 위해 트랜잭션 시작
4️⃣  영속성 컨텍스트에 저장된 상태, 아직 DB에 INSERT SQL 보내기 전
5️⃣  트랜잭션을 DB에 반영, 이 때 실제로 INSERT SQL 커밋 수행
6️⃣  엔티티 매니저와 엔티티 매니저 팩토리 자원을 close() 호출로 반환

 

 


 

Repository

1. Repository

  • `@Repository` : ORM을 사용하고자하는 클래스에 붙입니다.
    • `@Component`를 포함하고 있습니다. 앱 실행 시 생성 후 Bean으로 자동 등록됩니다.
  • Repository 기본 기능만 가진 구현체가 생성됩니다. (DB별 예외 처리 등)
  • 직접 `Entity Manager`를 사용하여, 영속성 컨텍스트를 사용해야 합니다.
더보기

Repository 간단 구현

// UserRepository.java
@Repository
public class UserRepository {

  @PersistenceContext
  EntityManager entityManager;

  public User insertUser(User user) {
    entityManager.persist(user);
    return user;
  }

  public User selectUser(Long id) {
    return entityManager.find(User.class, id);
  }
}
  • EntityManager 맴버 변수를 직접적을 사용합니다.

 

 

2. JpaRepository

  • `JpaRepository<Entity, ID 타입>` : ORM을 사용하고자하는 인터페이스에 상속시킵니다.
    • `@NoRepositoryBean`을 포함하고 있습니다. 상위 인터페이스들의 기능을 포함한 구현체가 프로그래밍됩니다.
      • Bean으로 생성되는 것을 막아줍니다. 이를 상속받아야만 생성되어 사용 가능해집니다. `JpaRepository<Entity, ID 타입>`을 상속받아야 하는 이유입니다.
    • `SpringDataJpa`에 의해 Entity의 CRUD, 페이징, 정렬 기능 메소드들을 가진 빈이 등록됩니다. (상위 인터페이스들의 기능)
  • Spring Data JPA의 `JpaRepository`는 자동 구현체를 제공합니다.
    • `EntityManager`를 직접 선언하거나 조작하지 않아도, Spring Data JPA가 자동으로 `SimpleJpaRepository`라는 구현체를 만들어줍니다. 그 클래스 내부적으로 `EntityManager`를 사용하도록 구현되어있습니다.
더보기

JpaRepository 간단 구현

// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
  // 기본 메서드는 자동으로 만들어짐
}
  • EntityMananger 맴버변수를 간접적으로 사용합니다.
  • CRUD, 페이징, 정렬 기능 메소드들을 자동으로 사용할 수 있게 됩니다.

 

💡`JpaRepository`를 상속받은 Repository는 다음과 같은 기능을 제공합니다.

  • 직접 영속성 컨텍스트를 사용하지 않습니다.
  • CRUD, 페이징, 정렬 등 다양한 기능을 직접 Query를 날리지 않아도 사용할 수 있습니다.

 


 

한계점

  • 복잡한 쿼리는 결국 `@Query`나 `QueryDSL로 작성해야 합니다.
  • 성능 튜닝이 어려운 경우도 있습니다. (특히 N+1, 지연로딩)
  • 완전한 DB 독립은 불가능합니다. (JPQL도 결국 DB마다 약간의 차이가 있습니다.)