본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로웹 개발 › 백엔드 › JPA

2. JPA 시작

목차

2. JPA 시작

JPA는 자바 객체와 관계형 데이터베이스 테이블 사이의 매핑 규약이다. 시작할 때 필요한 것은 JPA API, 이를 구현한 영속성 공급자, 데이터베이스 드라이버, 그리고 연결 설정이다. 라이브러리 버전과 설정 키는 사용하는 Jakarta Persistence 또는 기존 javax.persistence 환경에 맞춰 통일한다.

  1. 빌드 설정에 JPA 구현체와 JDBC 드라이버를 추가한다.
  2. META-INF/persistence.xml에 영속성 단위와 데이터베이스 연결 정보를 정의한다. 운영 환경에서는 비밀번호를 파일에 고정하지 않고 외부 설정으로 주입한다.
  3. 엔티티에 @Entity와 식별자 @Id를 선언한다.
  4. 트랜잭션 안에서 EntityManager로 저장하고 조회한다.
@Entity
public class Member {
    @Id
    private Long id;
    private String name;

    protected Member() {} // JPA가 사용하는 생성자
    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
    tx.begin();
    em.persist(new Member(1L, "Kim"));
    tx.commit();
} catch (RuntimeException e) {
    if (tx.isActive()) tx.rollback();
    throw e;
} finally {
    em.close();
}

위 코드는 자바 SE에서 EntityManager를 직접 관리하는 흐름이다. 스프링 같은 프레임워크에서는 트랜잭션과 생성 주기를 프레임워크가 관리할 수 있다. 예제를 실행하려면 먼저 실제 공급자에 맞는 persistence.xml과 데이터베이스가 준비되어야 한다.

저장한 엔티티 다시 읽기

EntityManager readEm = entityManagerFactory.createEntityManager();
try {
    Member found = readEm.find(Member.class, 1L);
    if (found == null) {
        System.out.println("해당 ID의 회원이 없습니다.");
    } else {
        System.out.println("회원 조회 성공");
    }
} finally {
    readEm.close();
}

find는 기본 키로 조회하고 결과가 없으면 null을 반환한다. 새 EntityManager를 사용했으므로 앞의 persist 트랜잭션이 성공적으로 커밋되어야 조회된다. EntityManagerFactory는 설정 단위로 오래 유지하고, 사용이 끝날 때 닫는다. 반면 EntityManager는 작업 단위로 만들고 다른 스레드와 공유하지 않는다.

flowchart LR
  코드[엔티티 생성] --> EM[EntityManager.persist]
  EM --> PC[영속성 컨텍스트]
  PC --> TX[트랜잭션 커밋]
  TX --> DB[(데이터베이스)]
  DB --> 조회[새 EntityManager.find]

처음 시도할 때는 persistence.xml의 영속성 단위 이름, JDBC 연결, 엔티티 검색 범위, 테이블 스키마가 맞는지 확인한다. 같은 기본 키를 다시 저장하면 충돌할 수 있으므로 예제를 여러 번 실행할 때는 테스트 데이터를 초기화하거나 ID 생성 전략을 정한다. 예외가 발생하면 앞 예제처럼 롤백한 뒤 원인을 확인하고, 실패한 EntityManager를 계속 재사용하지 않는다.

설정과 실행 경계 맞추기

persistence.xml은 src/main/resources/META-INF/persistence.xml에 둔다. 아래는 Jakarta Persistence API를 사용하는 구조 예시이며, 실제 JDBC URL·드라이버·공급자 버전과 테이블 생성 방식은 선택한 환경에 맞춰 채운다. javax.persistence 계열 의존성과 jakarta.persistence 계열 어노테이션을 섞으면 엔티티 탐색이나 클래스 로딩 단계에서 실패할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             version="3.0">
  <persistence-unit name="member-unit">
    <class>example.Member</class>
  </persistence-unit>
</persistence>

접속 값은 소스 파일에 두지 않고 실행 환경에서 공급한다. 공급자별 속성이 필요한 경우도 있지만, 표준 JDBC 연결 속성은 아래처럼 부팅 시 전달할 수 있다. 코드의 패키지명은 위 XML의 example.Member와 일치해야 한다.

import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import java.util.Map;

Map<String, Object> connection = Map.of(
    "jakarta.persistence.jdbc.driver", System.getenv("JDBC_DRIVER"),
    "jakarta.persistence.jdbc.url", System.getenv("JDBC_URL"),
    "jakarta.persistence.jdbc.user", System.getenv("JDBC_USER"),
    "jakarta.persistence.jdbc.password", System.getenv("JDBC_PASSWORD")
);
EntityManagerFactory emf =
    Persistence.createEntityManagerFactory("member-unit", connection);
try {
    // 앞 절의 EntityManager 저장·조회 코드를 여기서 실행
} finally {
    emf.close();
}

System.getenv()가 null이면 Map.of에서 바로 실패한다. 실제 프로그램에서는 필수 환경 변수의 존재를 시작 단계에서 검사하고 의미 있는 오류를 출력한다. 테스트용 로컬 DB라도 비밀번호를 문서나 Git에 고정하지 않는다. 테이블 Member가 없으면 공급자의 스키마 생성 설정을 개발 DB에만 사용하거나, 명시적 DDL을 먼저 실행한다. 운영 데이터에는 자동 create 설정을 적용하지 않는다.

sequenceDiagram
  participant App as Java SE 코드
  participant EMF as EntityManagerFactory
  participant EM as EntityManager
  participant DB as DB
  App->>EMF: persistence unit + 연결 속성
  App->>EM: createEntityManager()
  App->>EM: begin() / persist()
  EM->>DB: flush 시 INSERT
  App->>EM: commit()
  App->>EM: close()
  App->>EMF: close() (애플리케이션 종료)

EntityManagerFactory 생성은 비교적 무거우므로 요청마다 만들지 않는다. 위 그림의 EntityManager는 작업마다 만들고 예외가 나면 롤백하고 닫는다. flush는 DB에 SQL을 보내는 단계이며 commit은 트랜잭션 확정이다. flush 성공만으로 다른 트랜잭션에서 데이터가 보인다고 판단하면 안 된다.

실패를 단계별로 확인하기

연결 예외라면 URL·네트워크·권한을 보고, 시작 시 엔티티를 찾지 못하면 XML의 클래스명과 패키지를 확인한다. INSERT에서 실패하면 실제 DDL과 @Id·컬럼 제약을 비교한다. 조회가 null이면 저장 트랜잭션 커밋 여부와 사용 DB/스키마를 먼저 확인한다. 같은 ID로 예제를 반복 실행하면 기본 키 충돌이 나는 것은 정상적인 제약 동작이다. 테스트 데이터 정리 방식이나 생성 전략을 정해야 재실행 가능한 예제가 된다.

참고: Jakarta Persistence 사양, Persistence 부트스트랩 API, Spring Data JPA 트랜잭션.

같은 카테고리의 글