[Basic-1] JPA 시작

2026. 1. 5. 15:12·Spring/JPA

1. JPA란 무엇인가?

1.1. JPA의 등장 배경과 정의

JPA(Java Persistence API)는 자바 진영의 ORM(Object-Relational Mapping) 기술 표준이다. JPA가 등장하게 된 근본적인 이유는 기존 데이터 접근 방식의 여러 한계를 해결하기 위해서다.

기존 방식의 문제점 (JDBC/JdbcTemplate/MyBatis):

// 전통적인 JDBC 방식의 코드
public class MemberRepository {

    public Member findById(Long id) throws SQLException {
        String sql = "SELECT id, name, age FROM member WHERE id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery();

            if (rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                member.setAge(rs.getInt("age"));
                // 객체 그래프를 완성하려면 추가 쿼리 필요
                // member.setTeam(findTeamByMemberId(id));
                return member;
            }
            return null;
        } finally {
            close(conn, pstmt, rs);
        }
    }

    public void update(Member member) throws SQLException {
        String sql = "UPDATE member SET name = ?, age = ? WHERE id = ?";
        // Connection, PreparedStatement, executeUpdate...
        // 변경된 필드만 업데이트하는 게 아니라 모든 필드 업데이트
    }
}

기존 기술들의 근본적인 문제점:

  1. 반복적인 보일러플레이트 코드 - 매번 Connection, Statement, ResultSet을 열고 닫아야 함
  2. SQL 의존적인 개발 - 비즈니스 로직이 SQL문과 강하게 결합됨
  3. 패러다임 불일치 문제 - 객체지향과 관계형 데이터베이스의 근본적인 차이
  4. 데이터베이스 종속성 - 데이터베이스 벤더 변경 시 SQL 수정 필요
  5. 객체 그래프 탐색의 어려움 - 연관된 객체를 조회하려면 매번 추가 쿼리 실행

1.2. ORM(Object-Relational Mapping) 기술

ORM은 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 기술이다. 객체지향 프로그래밍은 클래스와 객체를 사용하고, 관계형 데이터베이스는 테이블과 행을 사용하는데, 이 두 패러다임 간의 간극을 메워준다.

// 객체지향 세계의 클래스 구조
class Member {
    private Long id;
    private String name;
    private int age;
    private Team team;  // 객체 참조
    private List<Order> orders;  // 객체 컬렉션
}

class Team {
    private Long id;
    private String teamName;
    private List<Member> members;
}

// 관계형 데이터베이스의 테이블 구조
-- MEMBER 테이블
CREATE TABLE member (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255),
    age INT,
    team_id BIGINT,  -- 외래키
    FOREIGN KEY (team_id) REFERENCES team(id)
);

-- TEAM 테이블
CREATE TABLE team (
    id BIGINT PRIMARY KEY,
    team_name VARCHAR(255)
);

패러다임 불일치의 구체적 문제들:

  1. 상속 문제 - 객체는 상속이 있지만, 테이블은 상속이 없음
  2. 연관관계 문제 - 객체는 참조를 사용하지만, 테이블은 외래키를 사용
  3. 데이터 타입 문제 - 객체는 다양한 데이터 타입을 가질 수 있지만, 테이블은 제한적
  4. 식별자 문제 - 객체는 동일성(identity)과 동등성(equality)을 구분하지만, 테이블은 기본키로 식별

1.3. JPA vs SQL Mapper(JdbcTemplate, MyBatis)

특징  JPA (ORM)  SQL Mapper (JdbcTemplate, MyBatis)
접근 방식 객체 중심 SQL 중심
생산성 높음 (반복 코드 최소화) 중간
유지보수 용이 (객체 수정 시 자동 반영) SQL 직접 관리 필요
패러다임 불일치 해결 자동 해결 개발자가 수동 해결
데이터베이스 독립성 높음 (Dialect 사용) 낮음 (SQL 직접 작성)
학습 곡선 가파름 완만함
복잡한 쿼리 JPQL 또는 네이티브 SQL 필요 SQL 직접 작성 가능

JPA의 핵심 가치:

  • 객체와 테이블을 매핑하는 설정만으로 SQL을 자동 생성
  • 객체 중심의 개발로 패러다임 불일치 해결
  • 데이터베이스 변경에 대한 추상화 제공
// JPA를 사용한 코드
@Entity
public class Member {
    @Id
    private Long id;
    private String name;
    private int age;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

// 저장
public void save(Member member) {
    entityManager.persist(member);  // INSERT SQL 자동 생성
}

// 조회
public Member findById(Long id) {
    return entityManager.find(Member.class, id);  // SELECT SQL 자동 생성
}

// 연관 객체 조회 (지연 로딩)
public void printMemberTeam(Long memberId) {
    Member member = entityManager.find(Member.class, memberId);
    System.out.println(member.getName());
    System.out.println(member.getTeam().getName());  // 필요할 때만 Team 조회
}

2. JPA 구동 방식

2.1. JPA 아키텍처와 구성 요소

JPA는 계층형 아키텍처로 구성되어 있으며, 주요 컴포넌트들은 다음과 같은 관계를 가진다.

2.2. 구동 단계별 상세 설명

1단계: persistence.xml 설정 파일 로드

JPA는 META-INF/persistence.xml 파일을 통해 설정을 읽는다. 이 파일은 JPA의 모든 설정을 포함하는 핵심 파일이다.

<!-- META-INF/persistence.xml -->
<persistence version="2.2"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
    http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    
    <!-- 영속성 유닛 정의 -->
    <persistence-unit name="hello">
        
        <!-- JPA 구현체 설정 -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        
        <!-- 엔티티 클래스 등록 -->
        <class>com.example.Member</class>
        <class>com.example.Team</class>
        
        <!-- 데이터베이스 연결 속성 -->
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            
            <!-- 옵션 속성 -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

2단계: EntityManagerFactory 생성

EntityManagerFactory는 JPA의 진입점이자 데이터베이스당 하나 생성되는 무거운 객체다. 이 객체를 생성하는 데는 상당한 비용이 든다.

// EntityManagerFactory 생성 코드
public class JpaMain {

    public static void main(String[] args) {
        // persistence.xml의 persistence-unit 이름으로 팩토리 생성
        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("hello");

        try {
            // 애플리케이션 로직 실행
            runApplication(emf);
        } finally {
            // 애플리케이션 종료 시 팩토리 닫기
            emf.close();
        }
    }

    private static void runApplication(EntityManagerFactory emf) {
        // 비즈니스 로직 실행
    }
}

EntityManagerFactory의 특징:

  • 데이터베이스 연결 정보를 기반으로 생성
  • 스레드 안전(thread-safe)하므로 여러 스레드가 동시에 접근 가능
  • 애플리케이션 전체에서 한 번만 생성하고 공유해야 함
  • 생성 비용이 크므로 필요할 때만 생성하고 재사용

3단계: EntityManager 생성

EntityManagerFactory로부터 EntityManager를 생성한다. EntityManager는 실제 데이터베이스 작업을 수행하는 핵심 객체다.

public class MemberRepository {

    private final EntityManagerFactory emf;

    public MemberRepository(EntityManagerFactory emf) {
        this.emf = emf;
    }

    public void save(Member member) {
        // 요청마다 새로운 EntityManager 생성
        EntityManager em = emf.createEntityManager();

        // 트랜잭션 시작
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            // 엔티티 저장
            em.persist(member);

            // 트랜잭션 커밋
            tx.commit();
        } catch (Exception e) {
            // 예외 발생 시 롤백
            tx.rollback();
            throw e;
        } finally {
            // 작업 완료 후 EntityManager 닫기
            em.close();
        }
    }
}

EntityManager의 특징:

  • 데이터베이스 커넥션을 내부적으로 관리
  • 스레드에 안전하지 않음(not thread-safe)
  • 요청마다 생성하고 사용 후 반드시 닫아야 함
  • 1차 캐시, 쓰기 지연 SQL 저장소, 변경 감지 등의 기능 제공

2.3. 중요 주의사항

2.3.1. 엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유

// ❌ 잘못된 예시 - 매번 새로 생성
public class WrongExample {

    public void method1() {
        EntityManagerFactory emf1 =
            Persistence.createEntityManagerFactory("hello");
        // ... 작업
        emf1.close();
    }

    public void method2() {
        EntityManagerFactory emf2 =
            Persistence.createEntityManagerFactory("hello");
        // ... 작업
        emf2.close();
    }
}

// ✅ 올바른 예시 - 싱글톤으로 관리
@Component
public class JpaConfig {

    private static EntityManagerFactory emf;

    @PostConstruct
    public void init() {
        if (emf == null) {
            emf = Persistence.createEntityManagerFactory("hello");
        }
    }

    @PreDestroy
    public void destroy() {
        if (emf != null && emf.isOpen()) {
            emf.close();
        }
    }

    public EntityManager createEntityManager() {
        return emf.createEntityManager();
    }
}

2.3.2. 엔티티 매니저는 쓰레드간에 공유하지 않음 (사용하고 버려야 함)

// ❌ 위험한 예시 - EntityManager 공유
public class UnsafeMemberService {

    private EntityManager em;  // 인스턴스 변수로 공유

    public UnsafeMemberService(EntityManagerFactory emf) {
        this.em = emf.createEntityManager();  // 생성자에서 한 번만 생성
    }

    public void processConcurrentRequests() {
        // 여러 스레드가 동시에 이 em을 사용하면 문제 발생!
    }
}

// ✅ 안전한 예시 - 요청마다 생성
@Service
public class SafeMemberService {

    private final EntityManagerFactory emf;

    public SafeMemberService(EntityManagerFactory emf) {
        this.emf = emf;
    }

    @Transactional
    public void saveMember(Member member) {
        // Spring이 트랜잭션마다 적절한 EntityManager 생성/관리
        EntityManager em = getCurrentEntityManager();  // Spring이 관리
        em.persist(member);
    }
}

// Spring 없는 순수 JPA 사용 시
public class PureJpaService {

    private final EntityManagerFactory emf;

    public void businessMethod() {
        EntityManager em = emf.createEntityManager();  // 메서드마다 생성
        EntityTransaction tx = em.getTransaction();

        try {
            tx.begin();
            // 비즈니스 로직
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            throw e;
        } finally {
            em.close();  // 반드시 닫기
        }
    }
}

2.3.3. JPA의 모든 데이터 변경은 트랜잭션 안에서 실행

JPA는 트랜잭션을 기반으로 동작한다. 데이터 변경 작업(INSERT, UPDATE, DELETE)은 반드시 트랜잭션 안에서 실행해야 한다.

// ❌ 잘못된 예시 - 트랜잭션 없이 데이터 변경
public void saveWithoutTransaction(Member member) {
    EntityManager em = emf.createEntityManager();

    // 트랜잭션 없이 저장 시도
    em.persist(member);  // 예외 발생 또는 데이터 무결성 문제

    em.close();
}

// ✅ 올바른 예시 - 트랜잭션 내에서 데이터 변경
public void saveWithTransaction(Member member) {
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    try {
        tx.begin();  // 트랜잭션 시작

        // 트랜잭션 내에서 데이터 변경 작업
        em.persist(member);

        tx.commit();  // 변경 내용을 데이터베이스에 반영
    } catch (Exception e) {
        if (tx.isActive()) {
            tx.rollback();  // 예외 발생 시 롤백
        }
        throw e;
    } finally {
        em.close();
    }
}

// 조회 작업은 트랜잭션 없이도 가능 (단, 영속성 컨텍스트 필요 시 트랜잭션 필요)
public Member findById(Long id) {
    EntityManager em = emf.createEntityManager();

    try {
        // 조회만 하는 경우 트랜잭션 없이도 가능
        Member member = em.find(Member.class, id);
        return member;
    } finally {
        em.close();
    }
}

트랜잭션의 중요성:

  • 원자성(Atomicity): 모든 작업이 성공하거나 모두 실패
  • 일관성(Consistency): 데이터 무결성 유지
  • 격리성(Isolation): 동시 접근 제어
  • 지속성(Durability): 커밋된 변경 사항 영구 저장

4. 결론

JPA는 객체지향 프로그래밍과 관계형 데이터베이스 사이의 간극을 메우기 위해 등장한 ORM 기술의 표준이다. JDBC나 SQL Mapper와 비교했을 때 JPA의 가장 큰 차이는 객체 중심의 사고방식과 생산성 향상에 있다.

JPA의 핵심 가치:

  1. 객체와 테이블의 매핑 설정만으로 SQL 자동 생성
  2. 패러다임 불일치 문제 해결 (상속, 연관관계, 객체 그래프 탐색)
  3. 데이터베이스 독립성 제공
  4. 생산성 향상과 유지보수성 개선

적용 전략:

  • 단순 CRUD: JPA의 기본 기능으로 충분
  • 복잡한 조회: JPQL 또는 Criteria API 사용
  • 매우 복잡한 통계: 네이티브 SQL 활용
  • 동적 쿼리: Criteria API 또는 QueryDSL 사용

'Spring > JPA' 카테고리의 다른 글

[Basic-3] 엔터티 매핑 (Entity Mapping)  (0) 2026.01.06
[Basic-2] 영속성 컨텍스트 (Persistence Context)  (0) 2026.01.06
[Optimization-7] JPA: 벌크 연산 모니터링  (0) 2025.12.27
[Optimization-6] JPA: 벌크 연산과 성능 최적화  (0) 2025.12.27
[Optimization-5] JPA: 벌크 연산의 이해와 ID 생성 전략의 상관관계  (0) 2025.12.26
'Spring/JPA' 카테고리의 다른 글
  • [Basic-3] 엔터티 매핑 (Entity Mapping)
  • [Basic-2] 영속성 컨텍스트 (Persistence Context)
  • [Optimization-7] JPA: 벌크 연산 모니터링
  • [Optimization-6] JPA: 벌크 연산과 성능 최적화
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (241) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (16)
        • MSA 기본 (11)
        • MSA 아키텍처 (5)
      • Kafka (30) N
        • Core (18) N
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[Basic-1] JPA 시작
상단으로

티스토리툴바