7주차 Unit 3.2 — JPA 의 등장

Psj·2026년 6월 1일

F-lab

목록 보기
221/239

Unit 3.2 — JPA 의 등장

F-LAB JAVA · 7주차 · Phase 3 · JPA 입문
★ 깊이 파기 — 자바 진영 ORM 표준의 본질


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • JPA (Java Persistence API) 의 정의는?
  • 자바 진영 ORM 표준 인터페이스 의 의미는?
  • JPA 구현체 (Hibernate, EclipseLink) 는?
  • JPA vs Hibernate 차이는?
  • JSR (Java Specification Request) 은?
  • 어노테이션 기반 매핑 은?
  • SQL Mapper vs JPA 비교 표는?
  • JPA 가 SQL 을 완전히 대체하는가 ?
  • JPA SQL 확인 방법 (show-sql) 은?

🎯 핵심 한 문장

JPA (Java Persistence API) 는 자바 진영의 ORM 표준 인터페이스이며 Hibernate · EclipseLink 같은 구현체가 실제 동작을 담당하는 "인터페이스 + 구현" 구조로, 어노테이션 기반 매핑과 자동 SQL 생성을 제공해 SQL Mapper 의 한계를 해결하지만 복잡 통계는 여전히 네이티브 쿼리가 필요하고 show-sql 옵션으로 생성된 SQL 을 확인할 수 있다.
JPA (Java Persistence API) 는 자바 진영의 ORM 표준 인터페이스 로, JSR-338 명세로 정의된 자바 표준 (Jakarta EE 의 일부) 이다.
JPA 자체는 인터페이스/명세이고 실제 동작하는 구현체 는 — Hibernate (가장 대중적, 사실상 표준), EclipseLink (Eclipse 공식), DataNucleus, OpenJPA — 5주차의 DataSource (인터페이스) + HikariCP (구현체) 와 같은 구조다.
사용 방식은 어노테이션 기반 매핑@Entity, @Id, @Column, @ManyToOne, @OneToMany 등을 객체에 선언하면 JPA 가 SQL 을 자동 생성 하고 객체 ↔ DB 변환을 처리한다.
단 JPA 도 SQL 완전 대체는 아니다 — 복잡 통계·윈도우 함수·DB 특수 기능은 네이티브 쿼리 가 필요하며, 생성된 SQL 은 spring.jpa.show-sql=true + format-sql=true 로 확인해 디버깅한다.
5주차의 인터페이스/구현 분리 정신이 JPA 에서도 — 애플리케이션은 JPA (표준) 에만 의존하므로 Hibernate 를 EclipseLink 로 교체해도 코드 그대로다 (이론상).

비유 — JDBC 처럼 JPA 도 표준

JPA = JDBC 의 ORM 버전:

JDBC (6주차) 와 JPA 비교:

JDBC:
  - 자바의 DB 접근 표준 (인터페이스)
  - Connection, Statement, ResultSet
  - DB 드라이버가 구현

JPA:
  - 자바의 ORM 표준 (인터페이스)
  - EntityManager, @Entity, ...
  - Hibernate, EclipseLink 가 구현

같은 패턴:
  - 자바 표준 (javax.persistence 또는 jakarta.persistence)
  - 다양한 구현체
  - 인터페이스 의존 = OCP

핵심 차이:
  - JDBC: SQL 직접
  - JPA: SQL 자동 (객체 매핑)

어노테이션:
  - @Entity: 이 객체는 DB 매핑
  - @Id: PK
  - @Column: 컬럼 매핑
  - @OneToMany: 1:N 관계

자동 SQL:
  - find(id) → SELECT
  - persist(entity) → INSERT
  - 객체 수정 → 자동 UPDATE
  - remove → DELETE

복잡 쿼리 한계:
  - 윈도우 함수, CTE 등
  - 네이티브 쿼리 필요
  - 또는 Querydsl

SQL 확인:
  - show-sql: true
  - 디버깅 / 학습 / 튜닝

→ JPA = 자바 ORM 표준 인터페이스, Hibernate 구현체, 어노테이션 자동 SQL, 복잡 쿼리는 보조.


🧭 9개 섹션 로드맵

1. JPA 정의
2. 자바 표준 인터페이스
3. 구현체 (Hibernate 등)
4. JPA vs Hibernate
5. JSR 과 Jakarta
6. 어노테이션 기반 매핑
7. SQL Mapper vs JPA 비교
8. SQL 완전 대체 X
9. SQL 확인 (show-sql)

1️⃣ JPA 정의

1.1 정의

JPA (Java Persistence API):

  자바의 ORM 표준 명세:
    - 인터페이스 모음
    - 어노테이션 정의
    - 동작 방식 규정

  → 자바 진영의 ORM 표준

1.2 "Persistence" 의 의미

"Persistence":

  영속성 (지속성):
    - 데이터를 영구 저장
    - 메모리 → DB
    - JVM 종료 후에도

→ "지속되는 데이터 다루기"

1.3 명세 vs 구현

명세 vs 구현:

  JPA = 명세 (Spec):
    - 인터페이스
    - 약속
    - 동작 X (자체로는)

  Hibernate = 구현체:
    - JPA 명세 따라
    - 실제 SQL 생성
    - 실제 동작

→ "JPA 명세 + Hibernate 구현"

1.4 ILIC 의 맥락

// JPA 사용 (ILIC)
import jakarta.persistence.*;   // JPA (표준 인터페이스)

@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "bl_no", length = 50)
    private String blNo;
    
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;
    
    @OneToMany(mappedBy = "shipment")
    private List<ShipmentItem> items;
}

// 사용
@PersistenceContext
private EntityManager em;   // JPA 인터페이스

Shipment s = em.find(Shipment.class, 1L);   // SQL 자동
em.persist(s);                                // SQL 자동

// → JPA 인터페이스만 의존
// → 실제는 Hibernate 가 동작
class Customer {}
class ShipmentItem {}
class EntityManager {
    <T> T find(Class<T> c, Object id) { return null; }
    void persist(Object o) {}
}
@interface PersistenceContext {}

1.5 자기 점검 답변

JPA 의 정의는?

:
1. JPA:

  • 자바 ORM 표준
  1. Persistence:

    • 영속성 (지속 저장)
  2. 명세:

    • 인터페이스
  3. 구현체:

    • Hibernate 등

2️⃣ 자바 표준 인터페이스

2.1 표준의 의미

표준의 의미:

  JPA = 자바 공식 표준:
    - JSR (Java Specification Request)
    - Jakarta EE (구 Java EE)
    - 자바 진영 합의

→ 모든 자바 개발자의 약속

2.2 패키지

패키지:

  Java EE 시대:
    - javax.persistence.*

  Jakarta EE (현재):
    - jakarta.persistence.*

  Spring Boot 3+ 부터:
    - jakarta.* 사용

2.3 표준 인터페이스 / 어노테이션

표준 정의:

  핵심 인터페이스:
    - EntityManager (영속성 관리)
    - EntityManagerFactory (팩토리)
    - Query (쿼리)
    - TypedQuery<T> (타입 안전 쿼리)
    - EntityTransaction (트랜잭션)

  핵심 어노테이션:
    - @Entity, @Id, @GeneratedValue
    - @Column, @Table
    - @ManyToOne, @OneToMany, @ManyToMany
    - @JoinColumn, @JoinTable
    - + 30개 이상

2.4 표준의 가치

표준의 가치:

  1. 호환성:
     - 한 번 학습 → 모든 구현체 사용
     - 코드 이식성

  2. 안정성:
     - 표준 유지
     - 갑작스러운 변경 X

  3. 풍부한 생태계:
     - 학습 자료
     - 도구 (JPA Buddy 등)
     - 커뮤니티

→ 자바 진영의 큰 강점

2.5 자바 외 ORM 과 차이

자바 외 ORM 과 차이:

  Python SQLAlchemy:
    - 사실상 표준 (de facto)
    - 명세 X
    - 단일 라이브러리

  Ruby ActiveRecord:
    - Rails 내장
    - 사실상 표준
    - 명세 X

  Java JPA:
    - 공식 명세 (JSR)
    - 여러 구현체
    - 인터페이스 + 구현체 분리

→ 자바는 표준 중심

2.6 ILIC 의 맥락

// 표준 활용 (ILIC)

// JPA 표준 인터페이스
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceContext;

// Spring Boot 가 자동으로 Hibernate 주입
@Service
public class ShipmentService {
    @PersistenceContext
    private EntityManager em;   // JPA 인터페이스
    
    public Shipment get(Long id) {
        return em.find(Shipment.class, id);
        // em 은 인터페이스
        // 실제는 Hibernate 의 구현
    }
}

// 코드는 표준만 의존
// 구현체 (Hibernate) 모름
// → 5주차 DI 정신
class Shipment {}
class EntityManager {
    <T> T find(Class<T> c, Object id) { return null; }
}

2.7 자기 점검 답변

자바 진영 ORM 표준 인터페이스의 의미는?

:
1. 표준:

  • 자바 공식 (JSR)
  1. 패키지:

    • jakarta.persistence
  2. 인터페이스:

    • EntityManager 등
  3. 가치:

    • 호환성/안정성

3️⃣ 구현체 (Hibernate 등)

3.1 JPA 구현체 목록

JPA 구현체:

  1. Hibernate (가장 대중적)
     - Red Hat 후원
     - 사실상 표준
     - 압도적 점유율

  2. EclipseLink
     - Eclipse 공식
     - 참조 구현체 (Reference Impl)

  3. DataNucleus
     - 다양한 데이터 저장소
     - 다목적

  4. OpenJPA (Apache)
     - Apache 프로젝트
     - 덜 인기

→ Hibernate 가 압도적

3.2 Hibernate

Hibernate:

  - 자바 최초 ORM (2001)
  - JPA 등장 (2006) 전부터
  - JPA 명세에 영향 줌

  특징:
    - 풍부한 기능
    - 안정성
    - 강력한 캐시 (1차/2차)
    - HQL (Hibernate Query Language)

  → 사실상 자바 ORM = Hibernate

3.3 Spring Boot 의 기본

Spring Boot 의 기본:

  spring-boot-starter-data-jpa:
    - JPA 인터페이스
    - Hibernate (구현체)
    - Spring Data JPA (추상화)
    - HikariCP (Connection Pool)

→ Spring Boot = JPA + Hibernate

3.4 구현체 교체 (이론)

구현체 교체:

  이론적으로:
    - Hibernate → EclipseLink
    - 의존성 변경
    - 코드 그대로

  실제로:
    - Hibernate 고유 기능 사용 시 어려움
    - 일부 어노테이션 차이
    - 거의 안 함

→ 보통 Hibernate 고정

3.5 ILIC 의 맥락

// ILIC build.gradle (Spring Boot 3)
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    // 자동 포함:
    // - JPA 인터페이스 (jakarta.persistence)
    // - Hibernate 6.x (구현체)
    // - Spring Data JPA
    // - HikariCP
    
    runtimeOnly 'com.mysql:mysql-connector-j'
}

// ILIC 코드:
// - JPA 인터페이스 (표준) 만 사용
// - 실제는 Hibernate 가 동작
// - 102 테이블 매핑
// JPA 코드 (ILIC) — 표준 어노테이션
import jakarta.persistence.*;

@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    @GeneratedValue
    private Long id;
    // 표준 어노테이션 사용
    // 구현체 (Hibernate) 가 해석
}

3.6 자기 점검 답변

JPA 구현체 (Hibernate, EclipseLink) 는?

:
1. 목록:

  • Hibernate / EclipseLink / DataNucleus
  1. Hibernate:

    • 압도적
  2. Spring Boot:

    • 기본 포함
  3. 교체:

    • 이론, 실제 거의 X

4️⃣ JPA vs Hibernate

4.1 비교 표

항목JPAHibernate
성격명세 (인터페이스)구현체 (라이브러리)
패키지jakarta.persistenceorg.hibernate
어노테이션표준 (@Entity 등)표준 + 자기 (@DynamicUpdate 등)
쿼리 언어JPQLHQL (JPQL + 확장)
단독 사용X (구현체 필요)O (가능)
의존성인터페이스만인터페이스 + 구현

4.2 5주차 패턴

5주차 패턴 매핑:

  JPA = 인터페이스 (추상)
  Hibernate = 구현체 (구체)

  - 5주차 DI 정신
  - 5주차 DIP (인터페이스 의존)
  - 6주차 DataSource 와 같은 구조

4.3 어떤 걸 학습?

어떤 걸 학습:

  먼저:
    - JPA 표준 (인터페이스, 어노테이션)
    - 95% 표준만으로 충분

  깊이:
    - Hibernate 고유 기능
    - 캐시 (2차)
    - 성능 튜닝
    - 트러블슈팅

→ JPA 먼저 → Hibernate 깊이

4.4 코드에서 구분

// JPA 표준 (권장)
import jakarta.persistence.*;

@Entity
@Id
@Column

// Hibernate 고유 (필요 시)
import org.hibernate.annotations.*;

@DynamicUpdate    // Hibernate 만
@DynamicInsert    // Hibernate 만
@BatchSize(size = 100)   // Hibernate 만

4.5 호환성

호환성:

  JPA 표준만 사용:
    - 다른 구현체 교체 가능
    - 학습 비용 ↓
    - 안전

  Hibernate 고유 사용:
    - 다른 구현체 교체 어려움
    - 하지만 더 강력
    - Hibernate 종속

→ 보통 둘 다 (필요한 만큼)

4.6 ILIC 의 맥락

// ILIC 의 JPA + Hibernate 활용

// 1. 기본은 JPA 표준
@Entity
public class Shipment {
    @Id @GeneratedValue
    private Long id;
    
    @Column(name = "bl_no")
    private String blNo;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;
}

// 2. 필요 시 Hibernate 고유
@Entity
@org.hibernate.annotations.DynamicUpdate    // 변경된 컬럼만 UPDATE
@org.hibernate.annotations.BatchSize(size = 100)   // 1+N/100
public class Shipment {
    // ...
}

// → 90% JPA, 10% Hibernate
// → 균형
class Customer {}

4.7 자기 점검 답변

JPA vs Hibernate 차이는?

:
1. JPA:

  • 명세 (인터페이스)
  1. Hibernate:

    • 구현체
  2. 5주차 패턴:

    • 인터페이스 + 구현
  3. 학습:

    • JPA 먼저

5️⃣ JSR 과 Jakarta

5.1 JSR

JSR (Java Specification Request):

  자바 공식 표준 명세 요청:
    - JCP (Java Community Process)
    - 새 표준 제안 → 검토 → 승인
    - 명세 문서 발행

  JPA 관련 JSR:
    - JSR-220 (EJB 3.0 with JPA 1.0, 2006)
    - JSR-317 (JPA 2.0, 2009)
    - JSR-338 (JPA 2.1, 2013) / 2.2 (2017)
    - JPA 3.0 (Jakarta EE, 2020+)

5.2 Java EE → Jakarta EE

Java EE → Jakarta EE:

  2017: Oracle 이 Java EE 를 Eclipse Foundation 으로 이양
  이름 변경: Java EE → Jakarta EE
  패키지 변경: javax → jakarta
  
  Spring Boot 3.0+ : jakarta 사용
  Spring Boot 2.x : 여전히 javax

→ 점진적 마이그레이션

5.3 명세의 의미

명세의 의미:

  - 인터페이스 정의
  - 어노테이션 정의
  - 동작 규정
  - 호환성 보장

→ 모든 구현체가 따라야

5.4 JCP 의 역할

JCP (Java Community Process):

  - 자바 표준 관리 기구
  - JSR 검토 / 승인
  - Expert Group 운영
  - 명세 발행

→ 자바 표준의 보증

5.5 JPA 버전 진화

JPA 버전:

  JPA 1.0 (2006): EJB 3.0 과 함께
  JPA 2.0 (2009): @ElementCollection, JPQL 강화
  JPA 2.1 (2013): @ConstructorResult, @StoredProcedure
  JPA 2.2 (2017): Stream<T> 지원
  Jakarta Persistence 3.0 (2020): 패키지 jakarta
  3.1 (2022), 3.2 (2024) ...

→ 점진적 발전

5.6 ILIC 의 맥락

// JSR / Jakarta 영향 (ILIC)

// Spring Boot 3 (Jakarta)
import jakarta.persistence.*;   // jakarta.*

// Spring Boot 2 (Java EE)
import javax.persistence.*;     // javax.*

// 마이그레이션 시:
// 1. Spring Boot 2 → 3 업그레이드
// 2. javax → jakarta 일괄 변경
// 3. 호환성 테스트

// ILIC = Spring Boot 3+
// → jakarta.* 사용
class JakartaPersistence {}

5.7 자기 점검 답변

JSR (Java Specification Request) 은?

:
1. JSR:

  • 자바 표준 명세 요청
  1. JPA JSR:

    • 220 / 317 / 338
  2. Java EE → Jakarta:

    • 패키지 변경
  3. JCP:

    • 표준 관리

6️⃣ 어노테이션 기반 매핑

6.1 어노테이션 매핑

어노테이션 매핑:

  객체에 어노테이션 선언:
    - @Entity (이것은 엔티티)
    - @Id (PK)
    - @Column (컬럼 매핑)
    - @ManyToOne (1:N 의 N 측)
    - 등

→ XML 보다 간결

6.2 핵심 어노테이션

@Entity                           // ① 엔티티 선언
@Table(name = "shipments")        // ② 테이블명
public class Shipment {
    @Id                            // ③ PK
    @GeneratedValue(strategy = GenerationType.IDENTITY)   // ④ 자동 키
    private Long id;
    
    @Column(name = "bl_no", length = 50, nullable = false, unique = true)
    private String blNo;            // ⑤ 컬럼 매핑
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @ManyToOne(fetch = FetchType.LAZY)       // ⑥ 1:N 의 N
    @JoinColumn(name = "customer_id")        // ⑦ FK
    private Customer customer;
    
    @OneToMany(mappedBy = "shipment",         // ⑧ 1:N 의 1
               cascade = CascadeType.ALL)
    private List<ShipmentItem> items;
    
    @Enumerated(EnumType.STRING)              // ⑨ Enum 매핑
    private ShipmentStatus status;
}

enum ShipmentStatus { BOOKED, SHIPPED, DELIVERED }
class Customer {}
class ShipmentItem {}

6.3 매핑 어노테이션 분류

매핑 어노테이션 분류:

  엔티티 선언:
    @Entity, @Table

  필드 매핑:
    @Id, @GeneratedValue
    @Column, @Transient (제외)
    @Enumerated, @Temporal

  관계 매핑:
    @ManyToOne, @OneToMany
    @ManyToMany, @OneToOne
    @JoinColumn, @JoinTable

  상속 매핑:
    @Inheritance
    @DiscriminatorColumn
    @DiscriminatorValue

  + 라이프사이클 콜백, 검증 등

6.4 XML 매핑 (옛 방식)

<!-- XML 매핑 (거의 사용 X) -->
<entity class="com.ilic.Shipment">
    <table name="shipments"/>
    <attributes>
        <id name="id">
            <generated-value strategy="IDENTITY"/>
        </id>
        <basic name="blNo">
            <column name="bl_no"/>
        </basic>
    </attributes>
</entity>

<!-- 어노테이션이 압도적 -->

6.5 어노테이션의 장점

어노테이션 장점:

  - 코드와 함께 (직관)
  - 짧음
  - 타입 안전 (IDE 지원)
  - 리팩토링 쉬움

XML 단점:
  - 별도 파일
  - 길음
  - 타입 안전 X
  - 동기화 부담

→ 어노테이션 압도적

6.6 ILIC 의 맥락

// ILIC 의 엔티티 매핑 (실제)
@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "bl_no", length = 50, nullable = false, unique = true)
    private String blNo;
    
    @Column(name = "weight", precision = 10, scale = 2)
    private BigDecimal weight;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "status", length = 20)
    private ShipmentStatus status;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;
    
    @OneToMany(mappedBy = "shipment", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ShipmentItem> items = new ArrayList<>();
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @PrePersist
    void onCreate() {
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    void onUpdate() {
        this.updatedAt = LocalDateTime.now();
    }
}

// → ILIC 의 102 테이블이 이런 패턴
enum ShipmentStatus { BOOKED, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }
class Customer {}
class ShipmentItem {}

6.7 자기 점검 답변

어노테이션 기반 매핑은?

:
1. 어노테이션:

  • 객체에 선언
  1. 분류:

    • 엔티티/필드/관계/상속
  2. XML 대비:

    • 간결/직관
  3. 표준:

    • JPA 명세

7️⃣ SQL Mapper vs JPA 비교

7.1 비교 표

항목SQL Mapper (JdbcTemplate, MyBatis)JPA
SQL 작성개발자 직접JPA 자동 생성
매핑RowMapper 수동어노테이션 선언
객체 그래프수동 처리자동 (Lazy/Eager)
변경 감지UPDATE 직접Dirty Checking 자동
1차 캐시없음자동 (영속성 컨텍스트)
학습 곡선낮음높음
복잡 쿼리자유어려움 (네이티브)
DB 종속높음낮음 (Dialect)
생산성보통매우 높음

7.2 코드 비교

// SQL Mapper (JdbcTemplate)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    
    public Shipment get(Long id) {
        return jdbcTemplate.queryForObject(
            "SELECT * FROM shipments WHERE id = ?",
            (rs, n) -> {
                Shipment s = new Shipment();
                s.setId(rs.getLong("id"));
                s.setBlNo(rs.getString("bl_no"));
                // ... 모든 컬럼 매핑
                return s;
            },
            id);
    }
    
    public void update(Shipment s) {
        jdbcTemplate.update(
            "UPDATE shipments SET bl_no = ?, weight = ? WHERE id = ?",
            s.getBlNo(), s.getWeight(), s.getId());
    }
}

// JPA
@Repository
public interface ShipmentRepository extends JpaRepository<Shipment, Long> {
    // CRUD 자동 (findById, save, ...)
    // 추가 메서드 (메서드 이름 자동 SQL)
    List<Shipment> findByStatus(String status);
}

// 사용
Shipment s = shipmentRepository.findById(1L).orElseThrow();
s.setStatus("SHIPPED");
// 자동 UPDATE (Dirty Checking, 트랜잭션 commit 시)
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setStatus(String s) {}
    String getBlNo() { return null; }
    java.math.BigDecimal getWeight() { return null; }
    Long getId() { return null; }
}
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
    void update(String s, Object... a) {}
    interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
}
ShipmentRepository shipmentRepository;
interface JpaRepository<T, ID> { java.util.Optional<T> findById(ID id); }
interface ShipmentRepository extends JpaRepository<Shipment, Long> {
    java.util.List<Shipment> findByStatus(String status);
}

7.3 코드 줄 비교

코드 줄 비교 (단순 CRUD):

  SQL Mapper:
    - DAO ~50 줄
    - SQL 직접

  JPA:
    - @Entity 1 클래스
    - Repository 인터페이스 (메서드만)
    - ~5 줄

→ 90% 감소

7.4 객체 그래프 비교

// SQL Mapper: 1:N 처리
Shipment s = ...;
List<ShipmentItem> items = jdbcTemplate.query(
    "SELECT * FROM shipment_items WHERE shipment_id = ?",
    itemMapper, s.getId());
s.setItems(items);

// JPA: 1:N 자동
Shipment s = shipmentRepository.findById(1L).orElseThrow();
s.getItems();   // 자동 (Lazy) 또는 JOIN (Eager)
class Shipment {
    Long getId() { return null; }
    void setItems(java.util.List<ShipmentItem> items) {}
    java.util.List<ShipmentItem> getItems() { return null; }
}
class ShipmentItem {}
JdbcTemplate jdbcTemplate;
RowMapper<ShipmentItem> itemMapper;
ShipmentRepository shipmentRepository;
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> {}
interface ShipmentRepository {
    java.util.Optional<Shipment> findById(Long id);
}

7.5 적합 영역

적합 영역:

  SQL Mapper:
    - 통계 / 분석 / 리포트
    - 윈도우 함수, CTE
    - DB 특수 기능
    - 매우 동적인 쿼리

  JPA:
    - CRUD
    - 도메인 모델
    - 객체 그래프
    - 변경 감지 필요

→ 혼용 권장

7.6 ILIC 의 맥락

// ILIC 의 혼용 패턴

// 1. JPA 주력 (95%)
@Repository
public interface ShipmentRepository extends JpaRepository<Shipment, Long> {
    List<Shipment> findByCustomer_Id(Long customerId);
    List<Shipment> findByStatusAndCreatedAtBetween(
        String status, LocalDateTime from, LocalDateTime to);
}

// 2. JdbcTemplate 보조 (5%)
@Repository
public class ShipmentStatsDao {
    private final JdbcTemplate jdbcTemplate;
    
    public List<Map<String, Object>> regionalStats() {
        return jdbcTemplate.queryForList("""
            WITH region_stats AS (
                SELECT region, amount,
                       RANK() OVER (PARTITION BY region ORDER BY amount DESC) AS rk
                FROM shipments_view
            )
            SELECT * FROM region_stats WHERE rk <= 5
            """);
        // 윈도우 함수 + CTE
        // JPA 어려움
    }
}

// → 도구별 적합한 영역
class Shipment {}
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    java.util.List<java.util.Map<String, Object>> queryForList(String s) { return null; }
}
ShipmentRepository shipmentRepository;

7.7 자기 점검 답변

SQL Mapper vs JPA 비교는?

:
1. SQL:

  • 직접 vs 자동
  1. 매핑:

    • 수동 vs 어노테이션
  2. 객체 그래프:

    • 수동 vs 자동
  3. 혼용:

    • 권장

8️⃣ SQL 완전 대체 X

8.1 JPA 의 한계

JPA 의 한계:

  1. 복잡 통계
     - 윈도우 함수, CTE
     - 어려움

  2. DB 특수 기능
     - JSON 함수
     - 공간 데이터
     - DB 별 SQL

  3. 대량 배치
     - INSERT 1000건
     - JPA 비효율 (영속성 컨텍스트)

  4. 매우 동적
     - 조건이 매우 다양
     - Querydsl 도 한계

8.2 네이티브 쿼리

// JPA 의 네이티브 쿼리
@Query(value = """
    SELECT s.*, c.name AS customer_name,
           ROW_NUMBER() OVER (PARTITION BY s.customer_id ORDER BY s.created_at DESC) AS rn
    FROM shipments s
    JOIN customers c ON s.customer_id = c.id
    WHERE rn <= 5
    """, nativeQuery = true)
List<Object[]> findRecentByCustomer();

// SQL 직접 + JPA 통합
// 결과는 Object[] 또는 DTO 매핑
class Object {}
@interface Query { String value(); boolean nativeQuery(); }

8.3 JPQL

// JPQL (객체 지향 쿼리)
@Query("""
    SELECT s FROM Shipment s
    JOIN s.customer c
    WHERE c.region = :region
    ORDER BY s.createdAt DESC
    """)
List<Shipment> findByRegion(@Param("region") String region);

// JPA 가 SQL 로 변환
// 객체 그래프 활용 (JOIN s.customer)
class Shipment {}
@interface Query { String value(); }
@interface Param { String value(); }

8.4 Querydsl

// Querydsl (타입 안전, 동적)
QShipment s = QShipment.shipment;
QCustomer c = QCustomer.customer;

BooleanBuilder where = new BooleanBuilder();
if (status != null) where.and(s.status.eq(status));
if (minWeight != null) where.and(s.weight.gt(minWeight));
if (region != null) where.and(s.customer.region.eq(region));

List<Shipment> result = queryFactory
    .selectFrom(s)
    .join(s.customer, c)
    .where(where)
    .orderBy(s.createdAt.desc())
    .fetch();

// 동적 쿼리 강력

8.5 JdbcTemplate 보조

// 매우 복잡: JdbcTemplate (6주차)
public List<Map<String, Object>> complexReport() {
    return jdbcTemplate.queryForList("""
        WITH RECURSIVE category_tree AS (
            -- 재귀 CTE
            SELECT id, name, parent_id, 0 AS depth
            FROM categories WHERE parent_id IS NULL
            UNION ALL
            SELECT c.id, c.name, c.parent_id, ct.depth + 1
            FROM categories c
            JOIN category_tree ct ON c.parent_id = ct.id
        )
        SELECT * FROM category_tree
        """);
    // 재귀 CTE — JPA 거의 불가능
}
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    java.util.List<java.util.Map<String, Object>> queryForList(String s) { return null; }
}

8.6 적재적소

적재적소:

  단순 CRUD:
    - JPA Repository (자동)

  객체 그래프:
    - JPA + JPQL

  동적 조건:
    - Querydsl

  복잡 통계:
    - 네이티브 쿼리 (@Query nativeQuery=true)

  매우 복잡:
    - JdbcTemplate

→ 도구별 강점

8.7 ILIC 의 맥락

// ILIC 의 도구 활용 패턴

// 1. 단순 CRUD (JPA Repository)
shipmentRepository.findById(id);
shipmentRepository.findByStatus("SHIPPED");

// 2. 객체 그래프 (JPQL fetch join)
@Query("SELECT s FROM Shipment s JOIN FETCH s.customer WHERE s.status = :status")
List<Shipment> findWithCustomer(@Param("status") String status);

// 3. 동적 조건 (Querydsl)
queryFactory.selectFrom(shipment)
    .where(/* 동적 조건 */)
    .fetch();

// 4. 복잡 통계 (네이티브)
@Query(value = "SELECT region, COUNT(*) FROM shipments GROUP BY region", nativeQuery = true)
List<Object[]> regionStats();

// 5. 매우 복잡 (JdbcTemplate)
jdbcTemplate.queryForList("WITH ... ");

// → 각자 적합한 도구
class Shipment {}
ShipmentRepository shipmentRepository;
JdbcTemplate jdbcTemplate;
class JdbcTemplate { java.util.List queryForList(String s) { return null; } }
interface ShipmentRepository {
    java.util.Optional<Shipment> findById(Long id);
    java.util.List<Shipment> findByStatus(String s);
}

8.8 자기 점검 답변

JPA 가 SQL 을 완전히 대체하는가?

:
1. NO:

  • 한계 있음
  1. JPQL:

    • 객체 그래프
  2. Querydsl:

    • 동적
  3. 네이티브 / JdbcTemplate:

    • 복잡 통계

9️⃣ SQL 확인 (show-sql)

9.1 SQL 확인의 중요성

SQL 확인의 중요성:

  JPA 가 자동 생성하지만:
    - 어떤 SQL?
    - 효율적인가?
    - N+1?

  → 확인 필수
  → 디버깅 / 튜닝 / 학습

9.2 show-sql

# application.yml
spring:
  jpa:
    show-sql: true              # SQL 출력
    properties:
      hibernate:
        format_sql: true        # 보기 좋게
        use_sql_comments: true  # 주석 (JPQL 표시)

9.3 출력 예시

-- show-sql + format-sql
Hibernate: 
    select
        shipment0_.id as id1_0_,
        shipment0_.bl_no as bl_no2_0_,
        shipment0_.weight as weight3_0_,
        shipment0_.status as status4_0_,
        shipment0_.customer_id as customer_id5_0_
    from
        shipments shipment0_
    where
        shipment0_.id = ?

9.4 더 자세한 도구

더 자세한 도구:

  1. p6spy:
     - 실제 파라미터 값 표시
     - 실행 시간
     - 로그

  2. datasource-proxy:
     - 비슷한 기능
     - Spring Boot 통합

  3. JPA Buddy (IntelliJ 플러그인):
     - SQL 실시간 미리보기
     - 매핑 확인

9.5 p6spy 설정

// build.gradle
dependencies {
    implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'
}
# 결과 (p6spy)
2024-01-01 12:00:00.000 INFO  p6spy : 
  #1709283600000 | took 5ms | statement | 
  connection 1 | 
  url jdbc:mysql://localhost:3306/ilic 
  SELECT s.id, s.bl_no, s.weight FROM shipments s WHERE s.id = 1

→ ? 가 아니라 실제 값 (1) 표시

9.6 디버깅 활용

디버깅 활용:

  1. N+1 발견:
     - SELECT 가 N 번 반복?
     - fetch join 으로 해결

  2. 비효율 발견:
     - 너무 많은 컬럼?
     - 불필요한 JOIN?

  3. 학습:
     - JPA 가 어떻게 SQL 만드는가
     - 어노테이션의 효과
     - JPQL → SQL 변환

9.7 운영 주의

운영 주의:

  show-sql:
    - 개발에서만
    - 운영에선 OFF
    - 성능 영향 ↑
    - 로그 폭증

  운영:
    - 슬로우 쿼리 로그 (DB)
    - APM (모니터링 도구)
    - 필요 시만

9.8 ILIC 의 맥락

# ILIC application.yml

# 개발 (local/dev)
spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true

# 운영 (prod)
spring:
  jpa:
    show-sql: false
    # 운영은 슬로우 쿼리 + APM
// ILIC 의 SQL 확인 패턴
// 개발 시 show-sql 로 매핑 확인
// 의심스러우면 p6spy 활성화

// 예: N+1 발견 시
List<Shipment> all = shipmentRepository.findAll();
all.forEach(s -> s.getCustomer().getName());
// 로그:
// SELECT * FROM shipments (1번)
// SELECT * FROM customers WHERE id = ? (N번!)
// → N+1 확인 → fetch join 으로 해결
class Shipment { Customer getCustomer() { return null; } }
class Customer { String getName() { return null; } }
ShipmentRepository shipmentRepository;
interface ShipmentRepository { java.util.List<Shipment> findAll(); }

9.9 면접 단골 질문 매핑

Q핵심 답변
JPA?자바 ORM 표준
Persistence 의미?영속성
구현체?Hibernate / EclipseLink
JPA vs Hibernate?명세 vs 구현
JSR?자바 표준 명세
Java EE → Jakarta?패키지 변경
어노테이션 매핑?@Entity 등
vs SQL Mapper?추상화 수준
SQL 완전 대체?NO
SQL 확인?show-sql

9.10 자기 점검 체크리스트

정의

  • JPA

표준

  • JSR / Jakarta

구현체

  • Hibernate

vs Hibernate

  • 명세 vs 구현

어노테이션

  • 매핑

vs SQL Mapper

  • 비교

SQL 대체 X

  • 한계

SQL 확인

  • show-sql / p6spy

9.11 추가 심화 질문

Q1: JPA 와 EJB?

답:

  • EJB 3.0 에서 JPA 분리 (2006)
  • JPA 는 단독 가능
  • EJB 는 무거움
  • JPA 가 ORM 독립적

Q2: Hibernate 의 1차 캐시 vs 2차 캐시?

답:

  • 1차: 영속성 컨텍스트 (트랜잭션 범위)
  • 2차: SessionFactory (애플리케이션 범위)
  • 2차는 외부 캐시 (EhCache, Redis)
  • 일관성 주의

Q3: JPA 의 영속성 컨텍스트?

답:

  • EntityManager 의 1차 캐시
  • 같은 트랜잭션 내 객체 동일 (==)
  • Dirty Checking 기반
  • 트랜잭션 commit 시 flush

Q4: Hibernate 의 Dialect?

답:

  • DB 별 SQL 차이 처리
  • MySQLDialect, OracleDialect 등
  • 페이징 / 자동 키 등
  • Spring Boot 자동 감지

Q5: JPA 도입 결정 기준?

답:

  • 도메인 모델 위주 → JPA
  • 통계 / 리포트 중심 → SQL Mapper
  • 보통 둘 다 혼용
  • 팀 역량도 고려

🎯 핵심 요약 — 3줄 정리

1. JPA = 자바 진영 ORM 표준 인터페이스

  • JSR-338 명세 (Jakarta EE 일부)
  • 구현체: Hibernate (사실상 표준), EclipseLink 등
  • 5주차 인터페이스/구현 패턴 (DataSource 와 같은 구조)

2. SQL Mapper 와 차이

  • SQL: 직접 vs 자동
  • 매핑: RowMapper vs 어노테이션
  • 객체 그래프: 수동 vs 자동
  • 학습곡선: 낮음 vs 높음

3. SQL 완전 대체 X

  • 복잡 통계/윈도우/CTE → 네이티브 쿼리 또는 JdbcTemplate
  • show-sql / p6spy 로 생성 SQL 확인 (디버깅/튜닝)
  • 도구별 적합한 영역 (혼용)

📚 다음으로...

Unit 3.3 — JPA 동작 위치

이번 Unit에서 JPA 의 정의를 봤다면, 다음은 JPA 의 위치 (애플리케이션 ↔ JDBC 사이).

  • JPA = 애플리케이션과 JDBC 사이의 중간 계층
  • 내부적으로는 여전히 JDBC 사용
  • 6주차의 DataSource, ConnectionPool 그대로 활용
  • 그림으로 보는 계층

Phase 3 진행 상황

🌱 Phase 3 — JPA 입문
  ✅ Unit 3.1 SQL Mapper 의 한계
  ✅ Unit 3.2 JPA 의 등장 ★깊이 ← 여기
  ⏭ Unit 3.3 JPA 동작 위치
  ⏭ Unit 3.4 Spring Data JPA + Querydsl ★깊이

7주차 누적 진행

🗂️ Part A — 데이터 모델링과 ORM
  ✅ Phase 1 (5)
  ✅ Phase 2 (2)
  🌱 Phase 3 (2/4)

총: 9/24 Unit

★ 깊이 파기 — JPA 의 등장 완료

profile
Software Developer

0개의 댓글