7주차 Unit 2.2 — ORM 의 정의와 효과

Psj·2026년 6월 1일

F-lab

목록 보기
220/239

Unit 2.2 — ORM 의 정의와 효과

F-LAB JAVA · 7주차 · Phase 2 · ORM 패러다임
🏆 Phase 2 완주 — ORM 의 본질


📌 학습 목표

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

  • ORM (Object-Relational Mapping) 의 정의는?
  • ORM 의 동작 방식 (중간 매핑) 은?
  • ORM 의 효과 (자동 매핑/개발 속도/DB 무관) 는?
  • 대중적 ORM 라이브러리 들은?
  • 자바의 ORM (Hibernate/JPA) 은?
  • ORM 이 SQL 을 완전히 대체하는가 ?
  • ORM 의 단점 3가지 는?
  • N+1 문제 는?
  • ORM vs SQL Mapper 차이는?

🎯 핵심 한 문장

ORM (Object-Relational Mapping) 은 객체와 관계형 DB 사이의 5가지 미스매치를 프레임워크가 자동 매핑해 객체로만 작업하게 해주는 도구로, 자바의 Hibernate · Python 의 SQLAlchemy · Ruby 의 ActiveRecord 등이 대표적이며 개발 속도가 빨라지지만 SQL 을 완전히 대체하진 않고 학습곡선 · N+1 · 튜닝 같은 단점도 있다.
ORM (Object-Relational Mapping)객체와 관계형 DB 사이의 매핑을 자동화 하는 프레임워크다.
동작 방식은 — 객체는 객체대로 설계하고 DB 는 DB 대로 설계하면, ORM 프레임워크가 중간에서 5가지 미스매치 (모델링/상속/연관/식별/타입) 를 자동으로 변환 한다.
효과는 — (1) 매핑 코드 자동 생성, (2) 개발 속도 ↑, (3) DB 변경 시 코드 변경 ↓ (Dialect), (4) 객체지향적 코드 유지.
대표 ORM 은 — 자바의 Hibernate (JPA 구현체) · EclipseLink, Python 의 SQLAlchemy · Django ORM, Ruby 의 ActiveRecord, C# 의 Entity Framework — 모든 주요 언어가 ORM 을 가진다.
단 ORM 도 만능은 아니다 — SQL 을 완전히 대체하지 않으며 (복잡 통계는 네이티브 쿼리), 단점으로 (1) 학습곡선, (2) N+1 문제, (3) 성능 튜닝 어려움 이 있어 6주차 JdbcTemplate (SQL Mapper) 과 적절히 혼용한다.

비유 — 자동 번역기 (다국어 비서)

ORM = 자동 번역기:

상황:
  - 한국어 회사 (객체) ↔ 영어 회사 (DB)
  - 매번 손으로 번역 = 고통

ORM (자동 번역기):
  - 한국어 입력 → 영어 자동 변환
  - 양방향
  - 5가지 미스매치 모두

자동 매핑:
  - "이 객체 저장해" → INSERT SQL 자동
  - "이 객체 가져와" → SELECT SQL 자동
  - 사람은 객체로만

대중적 ORM (각 언어):
  - Java: Hibernate
  - Python: SQLAlchemy
  - Ruby: ActiveRecord
  - C#: Entity Framework
  → 모든 언어 ORM

번역기의 한계:
  - 복잡 문장 어색 (네이티브 SQL 필요)
  - 학습곡선 (배워야)
  - N+1 (비효율 번역)
  - 튜닝 어려움

자바 진영:
  - JPA = 인터페이스 (표준)
  - Hibernate = 구현체 (실제)
  - "JPA 명세 + Hibernate 구현"

→ ORM = 자동 매핑 프레임워크, 모든 언어에 존재, 만능은 아니다.


🧭 9개 섹션 로드맵

1. ORM 정의
2. 동작 방식 (중간 매핑)
3. ORM 효과
4. 대중적 ORM
5. 자바 진영 (Hibernate / JPA)
6. SQL 완전 대체 X
7. ORM 단점 3가지
8. N+1 문제
9. ORM vs SQL Mapper

1️⃣ ORM 정의

1.1 정의

ORM (Object-Relational Mapping):

  객체 (Object) 와 
  관계형 DB (Relational) 사이의
  매핑 (Mapping) 을 자동화하는 프레임워크

→ 5가지 미스매치 (Unit 2.1) 해결

1.2 "Mapping" 의 의미

"Mapping":

  객체 ↔ DB 행 매핑:
    - 객체의 필드 ↔ 테이블의 컬럼
    - 객체의 참조 ↔ FK
    - 객체의 컬렉션 ↔ 별도 테이블
    - 객체의 상속 ↔ 3가지 전략

→ 양방향 자동

1.3 패러다임

패러다임:

  객체:
    - 객체대로 설계
    - 도메인 모델
    - OOP 원칙

  DB:
    - DB 대로 설계
    - 정규화
    - SQL 표준

  ORM 이 중간:
    - 객체 ↔ DB 자동
    - 양쪽 그대로

1.4 ILIC 의 맥락

ORM 정의 (ILIC)

ILIC 의 객체:
  class Shipment {
    Long id;
    String blNo;
    Customer customer;
    List<ShipmentItem> items;
    BigDecimal weight;
  }

ILIC 의 DB:
  CREATE TABLE shipments (id, bl_no, customer_id, weight);
  CREATE TABLE customers (id, name);
  CREATE TABLE shipment_items (id, shipment_id, ...);

ORM (Hibernate) 의 역할:
  - shipment.save() → INSERT 자동
  - findById(1) → SELECT + 객체 변환
  - shipment.getItems() → JOIN 또는 별도 SELECT
  - 양방향 매핑

→ 개발자는 객체로만 작업

1.5 자기 점검 답변

ORM (Object-Relational Mapping) 의 정의는?

:
1. ORM:

  • 객체 ↔ RDB 매핑
  1. 자동:

    • 프레임워크가
  2. 패러다임:

    • 양쪽 그대로
  3. 목적:

    • 미스매치 해결

2️⃣ 동작 방식 (중간 매핑)

2.1 위치

ORM 의 위치:

┌─────────────────────────────────┐
│   Application (객체 코드)         │
└────────────┬────────────────────┘
             │ ORM API
             ↓
┌─────────────────────────────────┐
│         ORM (Hibernate)         │ ← 매핑
└────────────┬────────────────────┘
             │ JDBC API
             ↓
┌─────────────────────────────────┐
│            JDBC                 │
└────────────┬────────────────────┘
             ↓
┌─────────────────────────────────┐
│            DB                   │
└─────────────────────────────────┘

2.2 양방향

양방향:

객체 → DB:
  - save(객체)
  - ORM: 객체 → SQL (INSERT/UPDATE)
  - JDBC 로 실행

DB → 객체:
  - findById(id)
  - ORM: SELECT 실행
  - ResultSet → 객체

→ 자동 변환

2.3 메타데이터 기반

메타데이터 기반:

  어노테이션 / XML:
    - @Entity, @Id, @Column
    - 매핑 정보 명시

  ORM 이 읽어서:
    - SQL 생성
    - 객체 ↔ DB 변환

2.4 동작 예시

// 객체 정의 (메타데이터)
@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    private Long id;
    
    @Column(name = "bl_no")
    private String blNo;
}

// 사용
Shipment s = new Shipment();
s.setBlNo("BL001");

em.persist(s);
// ORM 이 자동:
// INSERT INTO shipments (bl_no) VALUES ('BL001');

Shipment found = em.find(Shipment.class, 1L);
// ORM 이 자동:
// SELECT * FROM shipments WHERE id = 1;
// ResultSet → Shipment 객체
class EntityManager {
    void persist(Object o) {}
    <T> T find(Class<T> c, Object id) { return null; }
}
EntityManager em;

2.5 ILIC 의 맥락

// ORM 동작 (ILIC, JPA 사용)
@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    @GeneratedValue
    private Long id;
    
    private String blNo;
    
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;
    
    @OneToMany(mappedBy = "shipment")
    private List<ShipmentItem> items;
}

// 사용 (객체로만)
Shipment s = new Shipment();
s.setBlNo("BL001");
s.setCustomer(customer);   // 참조
s.getItems().add(item);    // 컬렉션

shipmentRepository.save(s);
// JPA 가:
// 1. shipments INSERT
// 2. customer_id 자동 (FK)
// 3. shipment_items 별도 INSERT

Shipment found = shipmentRepository.findById(1L).orElseThrow();
found.getCustomer().getName();   // 객체 그래프 자동
found.getItems();                 // 컬렉션 자동
class Customer { String getName() { return null; } }
class ShipmentItem {}
ShipmentRepository shipmentRepository;
interface ShipmentRepository {
    Shipment save(Shipment s);
    java.util.Optional<Shipment> findById(Long id);
}
class Shipment {
    void setBlNo(String s) {}
    void setCustomer(Customer c) {}
    java.util.List<ShipmentItem> getItems() { return null; }
    Customer getCustomer() { return null; }
}

2.6 자기 점검 답변

ORM 의 동작 방식 (중간 매핑) 은?

:
1. 위치:

  • 애플리케이션 ↔ JDBC ↔ DB 사이
  1. 양방향:

    • 객체 → DB / DB → 객체
  2. 메타데이터:

    • 어노테이션 기반
  3. 자동:

    • SQL 생성

3️⃣ ORM 효과

3.1 효과 1 — 매핑 코드 자동

매핑 코드 자동:

  ORM 없으면:
    - 매 메서드마다 SQL + 매핑
    - 보일러플레이트
    - 시간 낭비

  ORM 있으면:
    - 어노테이션 한 번
    - SQL 자동
    - 매핑 자동

→ 보일러플레이트 ↓

3.2 효과 2 — 개발 속도

개발 속도:

  CRUD 작성 시간:
    - JDBC 직접: 30분
    - JdbcTemplate: 10분
    - JPA + Spring Data: 1분
       (Repository 인터페이스만)

→ 압도적

3.3 효과 3 — DB 변경 시 코드 ↓

DB 변경:

  MySQL → PostgreSQL 변경 시:
    - JDBC 직접: SQL 문법 차이 (LIMIT/AUTO_INCREMENT 등) 수정
    - JdbcTemplate: 동일
    - JPA: Dialect 만 변경 (보통 자동)

→ DB 무관성

3.4 효과 4 — 객체지향 유지

객체지향 유지:

  ORM 없으면:
    - 객체에 DB 코드 섞임
    - SoC 깨짐
    - 도메인 로직 흐려짐

  ORM 있으면:
    - 객체는 객체로
    - DB 무관
    - OOP 원칙

→ 코드 품질 ↑

3.5 효과 5 — 생산성

생산성:

  비즈니스 로직 집중:
    - 매핑/SQL 신경 X
    - 도메인에 집중
    - 시간 절약

→ 모던 백엔드 표준

3.6 ILIC 의 맥락

ORM 효과 (ILIC)

ILIC 가 ORM (JPA) 사용으로:

  1. 매핑 코드 ↓
     - 102 테이블 × 평균 10 메서드 = 1020 매핑 코드
     - JPA 로 어노테이션 + Repository
     - 코드 70% 감소

  2. 개발 속도 ↑
     - 새 테이블 추가:
       - JDBC: 100 줄
       - JPA: 20 줄

  3. DB 변경 가능
     - 만약 MySQL → PostgreSQL
     - Dialect 변경만
     - 대부분 코드 그대로

  4. 객체지향 유지
     - Shipment, Customer 등 도메인 객체
     - DB 코드 X
     - 깔끔

  5. 생산성
     - 박승제 같은 풀스택 개발자
     - 102 테이블도 관리 가능
     - JPA 가 있어서

→ ILIC = JPA 주력, JdbcTemplate 보조

3.7 자기 점검 답변

ORM 의 효과는?

:
1. 매핑 자동:

  • 보일러플레이트 ↓
  1. 개발 속도:

    • 압도적
  2. DB 무관:

    • Dialect
  3. 객체지향:

    • 유지

4️⃣ 대중적 ORM

4.1 자바

자바 ORM:

  - Hibernate (가장 대중)
  - EclipseLink
  - DataNucleus
  - OpenJPA

→ Hibernate 가 사실상 표준

4.2 Python

Python ORM:

  - SQLAlchemy (강력)
  - Django ORM (Django 통합)
  - Peewee (가벼움)
  - Tortoise ORM (async)

→ SQLAlchemy 가 가장 인기

4.3 Ruby

Ruby ORM:

  - ActiveRecord (Ruby on Rails)
    - 이름 자체가 패턴
    - Rails 와 통합
    - 매우 직관적

→ Rails = ActiveRecord

4.4 C# / .NET

C# ORM:

  - Entity Framework
    - Microsoft 공식
    - LINQ 통합
  - Entity Framework Core
    - 크로스 플랫폼
  - Dapper (Micro ORM)

→ EF 가 표준

4.5 기타 언어

기타:

  PHP: Doctrine, Eloquent
  Go: GORM, Ent
  Node.js: Sequelize, TypeORM, Prisma
  Rust: Diesel, SeaORM
  Kotlin: Exposed, Hibernate

→ 모든 주요 언어 ORM

4.6 공통 특징

공통 특징:

  - 객체 ↔ DB 자동 매핑
  - 어노테이션/데코레이터/매크로
  - CRUD 자동
  - 관계 (1:1, 1:N, N:M) 매핑
  - 쿼리 빌더

→ "객체로 작업" 표준

4.7 ILIC 의 맥락

ILIC 의 ORM 선택

ILIC = 자바 + Spring Boot
  → Hibernate (JPA 구현체)
  → Spring Data JPA 추가 (편의)
  → Querydsl (복잡 쿼리)

  실무 조합:
    Spring Data JPA + Querydsl + JPA + Hibernate

  주력:
    - Hibernate (실제 매핑)
    - Spring Data JPA (Repository 자동)
    - Querydsl (동적/복잡 쿼리)

  보조:
    - JdbcTemplate (네이티브 SQL 필요 시)

4.8 자기 점검 답변

대중적 ORM 라이브러리들은?

:
1. 자바:

  • Hibernate
  1. Python:

    • SQLAlchemy / Django ORM
  2. Ruby:

    • ActiveRecord (Rails)
  3. C#:

    • Entity Framework

5️⃣ 자바 진영 (Hibernate / JPA)

5.1 JPA 와 Hibernate

JPA 와 Hibernate:

  JPA (Java Persistence API):
    - 자바 ORM 표준 인터페이스
    - 명세 (Spec)
    - 구현체 X

  Hibernate:
    - JPA 구현체
    - 가장 인기
    - 실제 동작

→ "인터페이스 + 구현체"

5.2 JPA 의 역할

JPA 의 역할:

  - 표준 인터페이스
  - 어노테이션 정의
    (@Entity, @Id, @Column, ...)
  - 메서드 정의
    (persist, find, merge, ...)
  - 모든 구현체가 따름

→ DI/DataSource 정신 (인터페이스)

5.3 Hibernate 의 역할

Hibernate 의 역할:

  - JPA 인터페이스 구현
  - 실제 SQL 생성
  - 캐싱
  - Dirty Checking
  - Lazy Loading
  - + Hibernate 고유 기능

→ JPA + 추가

5.4 관계 그림

관계:

  Application
       ↓ (의존)
  JPA (인터페이스, javax.persistence.*)
       ↑ (구현)
  Hibernate (구현체)
       ↓
  JDBC → DB

→ 5주차 인터페이스 패턴
→ 6주차 DataSource 와 같은 정신

5.5 다른 구현체

다른 구현체:

  JPA 구현체:
    - Hibernate ★★★ (압도적)
    - EclipseLink (Eclipse 공식)
    - DataNucleus
    - OpenJPA

→ 인터페이스 의존, 구현체 교체 가능 (이론상)
→ 실무는 Hibernate

5.6 Spring Data JPA

Spring Data JPA:

  JPA 의 또 다른 추상화:
    - Repository 인터페이스 자동 구현
    - 메서드 이름 → SQL 자동
    - findByXxx, deleteByYyy

→ JPA 위의 또 다른 레이어
→ 다음 Phase 3

5.7 ILIC 의 맥락

ILIC 의 JPA 스택

ILIC:
  - Spring Boot 3
  - Spring Data JPA (의존성)
    → 자동으로 Hibernate (JPA 구현체)
    → 자동으로 JPA
    → 자동으로 JDBC + HikariCP

  스택:
    Application
      ↓
    Spring Data JPA (메서드 이름 자동 쿼리)
      ↓
    JPA (인터페이스)
      ↓
    Hibernate (실제 구현, SQL 생성)
      ↓
    JDBC (6주차)
      ↓
    HikariCP (Connection Pool, 6주차)
      ↓
    MySQL

  → 깊은 추상화 스택

5.8 자기 점검 답변

자바의 ORM (Hibernate/JPA) 은?

:
1. JPA:

  • 인터페이스 (표준)
  1. Hibernate:

    • JPA 구현체
  2. 관계:

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

    • 더 추상화

6️⃣ SQL 완전 대체 X

6.1 ORM 의 한계

ORM 의 한계:

  - 단순 CRUD: ORM 잘 함
  - 복잡 쿼리: 어려움
    - 동적 쿼리
    - 복잡 집계
    - 윈도우 함수
    - DB 별 특수 기능

→ SQL 도 알아야

6.2 네이티브 쿼리

// 네이티브 쿼리 (JPA)
@Query(value = """
    SELECT s.*, c.name 
    FROM shipments s
    JOIN customers c ON s.customer_id = c.id
    WHERE EXISTS (
        SELECT 1 FROM freights f
        WHERE f.shipment_id = s.id
        AND f.amount > (
            SELECT AVG(amount) FROM freights
        )
    )
    """, nativeQuery = true)
List<Object[]> findHighValueShipments();

// JPA 가 못 자동 생성
// SQL 직접
class Object {}

6.3 JPQL

// JPQL (Java Persistence Query Language)
// - 객체 지향 쿼리
// - 테이블 X, 엔티티 사용
@Query("""
    SELECT s FROM Shipment s
    JOIN s.customer c
    WHERE c.region = :region
    """)
List<Shipment> findByRegion(@Param("region") String region);

// JPQL → SQL 변환
// 객체 그래프 활용
class Shipment {}
@interface Param { String value(); }

6.4 Querydsl

// Querydsl (타입 안전 쿼리 빌더)
List<Shipment> result = queryFactory
    .selectFrom(shipment)
    .join(shipment.customer, customer)
    .where(
        shipment.weight.gt(new BigDecimal("100"))
        .and(customer.region.eq("Asia"))
    )
    .fetch();

// 컴파일 시점 타입 체크
// 동적 쿼리 강력

6.5 혼용

혼용:

  - 단순 CRUD: JPA Repository
  - 객체 그래프: JPQL
  - 복잡 동적: Querydsl
  - 네이티브: @Query(native)
  - 매우 복잡: JdbcTemplate (6주차)

→ 도구 적절히

6.6 ILIC 의 맥락

// ILIC 의 혼용 패턴

// 1. 단순 CRUD: Spring Data JPA
interface ShipmentRepository extends JpaRepository<Shipment, Long> {
    List<Shipment> findByStatus(String status);
    // 자동 SQL: WHERE status = ?
}

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

// 3. 동적: Querydsl
QShipment s = QShipment.shipment;
BooleanBuilder where = new BooleanBuilder();
if (status != null) where.and(s.status.eq(status));
if (minWeight != null) where.and(s.weight.gt(minWeight));
queryFactory.selectFrom(s).where(where).fetch();

// 4. 복잡 통계: JdbcTemplate
jdbcTemplate.queryForList("""
    SELECT region, COUNT(*), AVG(amount), 
           PERCENT_RANK() OVER (PARTITION BY region ORDER BY amount)
    FROM shipment_view
    GROUP BY region
    """);

// → 각자 적합한 도구
class Shipment {}
class QShipment { static QShipment shipment = new QShipment(); }
class BooleanBuilder { BooleanBuilder and(Object o) { return this; } }

6.7 자기 점검 답변

ORM 이 SQL 을 완전히 대체하는가?

:
1. NO:

  • 한계 있음
  1. JPQL:

    • 객체 지향 쿼리
  2. Querydsl:

    • 동적/타입 안전
  3. 혼용:

    • JdbcTemplate 보조

7️⃣ ORM 단점 3가지

7.1 단점 1 — 학습곡선

학습곡선:

  ORM 익숙해지기:
    - 어노테이션 (수십 개)
    - 영속성 컨텍스트
    - Lazy Loading
    - 트랜잭션 통합
    - 캐시 동작

  - 3-6개월 정도
  - 깊은 이해 더 오래

→ 진입 장벽 ↑

7.2 단점 2 — N+1 문제

N+1 문제:

  목록 조회 1 + 각 항목별 1 + ... = 1+N
  
  대량 데이터:
    - 1000 고객 + 각자 배송
    - 1001 쿼리 발생
    - 성능 폭망

→ 가장 흔한 함정

7.3 단점 3 — 성능 튜닝 어려움

성능 튜닝:

  ORM 추상화의 비용:
    - 어떤 SQL 생성?
    - 왜 느린가?
    - 인덱스 활용?
    - Fetch 전략?

  - SQL 직접보다 어려움
  - JPA 의 내부 이해 필요
  - 로깅으로 SQL 확인

→ 깊은 이해 필요

7.4 다른 단점들

다른 단점들:

  - 복잡 쿼리 표현 어려움
  - 캐시 일관성 (1차/2차)
  - 마이그레이션 의존
  - 라이브러리 의존성

7.5 단점 해결

단점 해결:

  학습곡선:
    - 점진적 학습
    - 책/강의

  N+1:
    - fetch join
    - @EntityGraph
    - Batch fetch

  성능 튜닝:
    - show-sql / p6spy
    - JPA Buddy 등 도구
    - 실무 경험

7.6 ILIC 의 맥락

ILIC 의 ORM 단점 대응

학습곡선:
  - 박승제도 충분히 학습 후 사용
  - 팀원도 점진적
  - 6주차 (JDBC/JdbcTemplate) 가 기반

N+1:
  - Spring Data JPA + fetch join
  - @EntityGraph
  - 로깅 (spring.jpa.show-sql)
  - 코드 리뷰에서 체크

성능 튜닝:
  - show-sql + format-sql
  - p6spy 또는 datasource-proxy
  - 슬로우 쿼리 모니터링
  - 인덱스 / EXPLAIN
  - 복잡 쿼리는 JdbcTemplate 보완

→ JPA 활용 + 도구 + 경험

7.7 자기 점검 답변

ORM 의 단점 3가지는?

:
1. 학습곡선:

  • 진입 장벽
  1. N+1:

    • 흔한 함정
  2. 튜닝 어려움:

    • 내부 이해
  3. 해결:

    • 점진적/도구/경험

8️⃣ N+1 문제

8.1 N+1 정의

N+1 문제:

  목록 1 쿼리 + 각 항목별 N 쿼리:
    1 + N 쿼리

  ORM 의 함정:
    - 자동 매핑이 너무 친절
    - 무심코 발생

8.2 발생 시나리오

// N+1 발생
List<Customer> customers = customerRepository.findAll();  // 1 쿼리
// SELECT * FROM customers;

for (Customer c : customers) {
    System.out.println(c.getShipments().size());
    // → 각자 SELECT * FROM shipments WHERE customer_id = ?;
    // → N 쿼리
}
// 총: 1 + N (고객 1000명이면 1001 쿼리!)
class Customer { java.util.List<?> getShipments() { return null; } }
CustomerRepository customerRepository;
interface CustomerRepository { java.util.List<Customer> findAll(); }

8.3 해결 1 — fetch join

// fetch join (한 번에)
@Query("""
    SELECT c FROM Customer c
    LEFT JOIN FETCH c.shipments
    """)
List<Customer> findAllWithShipments();

// 1 쿼리 (LEFT JOIN)
// SELECT c.*, s.* FROM customers c LEFT JOIN shipments s ON ...
class Customer {}
@interface Query { String value(); }

8.4 해결 2 — @EntityGraph

// @EntityGraph (Spring Data JPA)
@EntityGraph(attributePaths = {"shipments"})
List<Customer> findAll();

// 자동 fetch join
class Customer {}
@interface EntityGraph { String[] attributePaths(); }

8.5 해결 3 — Batch Fetch

# application.yml
spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100

# → IN 절로 100 개씩 한 번에
# → 1 + N/100 쿼리

8.6 진단

진단:

  show-sql:
    - SQL 로그 확인
    - 의외로 많이 발생

  p6spy:
    - 실제 SQL + 시간
    - 더 자세

  슬로우 쿼리:
    - DB 로그
    - 운영 모니터링

→ "왜 이렇게 많이?" 가 N+1 신호

8.7 ILIC 의 맥락

// ILIC 의 N+1 대응

// 위험 코드
List<Customer> customers = customerRepository.findAll();
customers.forEach(c -> {
    log.info("고객 {}: 배송 {} 건", c.getName(), c.getShipments().size());
    // 각 고객마다 SELECT
});

// 해결 1: fetch join
@Query("""
    SELECT c FROM Customer c
    LEFT JOIN FETCH c.shipments
    """)
List<Customer> findAllWithShipments();

// 해결 2: 별도 조회
List<Customer> customers = customerRepository.findAll();
List<Long> customerIds = customers.stream().map(Customer::getId).toList();
Map<Long, List<Shipment>> shipmentMap = shipmentRepository
    .findByCustomerIdIn(customerIds)
    .stream()
    .collect(Collectors.groupingBy(s -> s.getCustomer().getId()));
// 1 + 1 쿼리

// 해결 3: 배치 fetch
// application.yml 설정만으로 자동

// ILIC 의 표준:
// - default_batch_fetch_size: 100
// - + 필요 시 fetch join
class Customer {
    Long getId() { return null; }
    String getName() { return null; }
    java.util.List<Shipment> getShipments() { return null; }
}
class Shipment {
    Customer getCustomer() { return null; }
}
org.slf4j.Logger log;
ShipmentRepository shipmentRepository;
interface ShipmentRepository {
    java.util.List<Shipment> findByCustomerIdIn(java.util.List<Long> ids);
}
CustomerRepository customerRepository;

8.8 자기 점검 답변

N+1 문제는?

:
1. N+1:

  • 1 + N 쿼리
  1. 시나리오:

    • 목록 + 각 항목
  2. 해결:

    • fetch join / batch / @EntityGraph
  3. 진단:

    • show-sql / 로깅

9️⃣ ORM vs SQL Mapper

9.1 비교 표

항목SQL Mapper (JdbcTemplate, MyBatis)ORM (JPA, Hibernate)
SQL 작성개발자 직접ORM 이 자동 생성
매핑RowMapper 수동어노테이션 자동
객체 그래프수동 처리자동 (Lazy Loading)
학습 곡선낮음높음
복잡 쿼리자유어려움 (네이티브)
캐시직접 구현1차/2차 자동
Dirty CheckingX자동
변경 감지UPDATE 명시자동

9.2 SQL Mapper 강점

SQL Mapper 강점:

  - SQL 직접 (자유)
  - 복잡 쿼리 강력
  - 학습 ↓
  - 성능 예측 쉬움
  - DB 특수 기능 활용

→ 분석/리포트/통계에 강함

9.3 ORM 강점

ORM 강점:

  - 객체로 작업
  - CRUD 자동
  - 객체 그래프
  - 영속성 컨텍스트
  - 변경 자동 감지

→ 도메인 로직에 강함

9.4 선택 기준

선택 기준:

  CRUD 위주 / 도메인 모델:
    - ORM (JPA)

  복잡 쿼리 / 분석 / 통계:
    - SQL Mapper (JdbcTemplate)
    - 또는 JPA + 네이티브 쿼리

  실무:
    - JPA 주력 + JdbcTemplate 보조
    - 적절히 혼용

9.5 6주차 ↔ 7주차

6주차 ↔ 7주차:

6주차:
  - JdbcTemplate (SQL Mapper)
  - SQL 직접
  - 매핑 (RowMapper)

7주차:
  - JPA (ORM)
  - SQL 자동
  - 매핑 (어노테이션)

→ 같은 문제 다른 추상화 수준
→ 둘 다 알아야

9.6 ILIC 의 맥락

// ILIC 의 SQL Mapper + ORM 혼용

// 1. 단순 CRUD: JPA (주력)
interface ShipmentRepository extends JpaRepository<Shipment, Long> {
    List<Shipment> findByStatus(String status);
}

// 2. 도메인 그래프: JPA
@Entity
class Shipment {
    @OneToMany List<ShipmentItem> items;
}
Shipment s = repo.findById(1L);
s.getItems();  // 자동

// 3. 복잡 통계: JdbcTemplate (보조)
public List<Map<String, Object>> complexStats() {
    return jdbcTemplate.queryForList("""
        WITH ranked AS (
            SELECT region, amount,
                   RANK() OVER (PARTITION BY region ORDER BY amount DESC) AS rk
            FROM shipments
        )
        SELECT * FROM ranked WHERE rk <= 5
        """);
    // 윈도우 함수 + CTE
    // JPA 로 표현 어려움
}

// → 도구 적절히 혼용
// → ILIC 의 표준 패턴
class Shipment {
    java.util.List<ShipmentItem> getItems() { return null; }
}
class ShipmentItem {}
interface JpaRepository<T, ID> {
    java.util.Optional<T> findById(ID id);
}
class JdbcTemplate {
    java.util.List<java.util.Map<String, Object>> queryForList(String s) { return null; }
}
JdbcTemplate jdbcTemplate;
ShipmentRepository repo;

9.7 면접 단골 질문 매핑

Q핵심 답변
ORM?객체 ↔ RDB 매핑
효과?자동 / 속도 / DB 무관
대중적?Hibernate/SQLAlchemy/AR
JPA vs Hibernate?인터페이스 vs 구현
SQL 대체?NO (복잡은 직접)
단점?학습/N+1/튜닝
N+1?1+N 쿼리
vs SQL Mapper?추상화 수준
Spring Data?JPA 위 추상화
혼용?적절히

9.8 자기 점검 체크리스트

정의

  • ORM

동작

  • 중간 매핑

효과

  • 4가지

대중적

  • 언어별

자바

  • Hibernate/JPA

SQL 대체 X

  • NO

단점

  • 3가지

N+1

  • 진단/해결

vs SQL Mapper

  • 혼용

9.9 추가 심화 질문

Q1: Active Record vs Data Mapper 패턴?

답:

  • Active Record: 객체 자체에 DB 메서드 (Ruby AR, Eloquent)
  • Data Mapper: 별도 매퍼 (JPA, Hibernate)
  • JPA = Data Mapper
  • 객체 순수성 ↑

Q2: ORM 의 1차/2차 캐시?

답:

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

Q3: Lazy Loading vs Eager Loading?

답:

  • Lazy: 필요 시 로딩 (기본)
  • Eager: 즉시 로딩
  • Lazy 가 일반 (N+1 주의)
  • @ManyToOne 은 Eager 기본 (변경 권장)

Q4: Dirty Checking?

답:

  • 영속 객체 변경 자동 감지
  • 트랜잭션 commit 시 UPDATE 생성
  • UPDATE 명시 불필요
  • JPA 의 강력한 기능

Q5: ORM 의 적합/부적합?

답:

  • 적합: 도메인 모델, CRUD 위주
  • 부적합: 분석/리포트, 대량 배치
  • 보통 혼용

🎯 핵심 요약 — 3줄 정리

1. ORM 정의와 효과

  • 객체 ↔ RDB 5가지 미스매치를 프레임워크가 자동 매핑
  • 효과: 매핑 코드 자동, 개발 속도 ↑, DB 무관, 객체지향 유지

2. 대중적 ORM

  • 자바: Hibernate (JPA 구현체)
  • Python: SQLAlchemy, Django ORM
  • Ruby: ActiveRecord, C#: Entity Framework
  • 모든 주요 언어가 ORM 지원

3. 만능 아님

  • SQL 완전 대체 X (복잡 쿼리는 네이티브/JdbcTemplate)
  • 단점: 학습곡선, N+1 문제, 성능 튜닝 어려움
  • 실무: JPA 주력 + JdbcTemplate 보조

🏆 Phase 2 완주 — ORM 패러다임

🔄 Phase 2 — ORM 패러다임
  ✅ Unit 2.1 객체-관계 미스매치
  ✅ Unit 2.2 ORM 의 정의와 효과 ← 여기, Phase 2 완주

→ 5가지 미스매치 이해
→ ORM 의 본질 이해
→ Phase 3 (JPA 입문) 의 기반

📚 다음으로...

Phase 3 — JPA 입문

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

Phase 3 주제:

  • SQL Mapper (JdbcTemplate) 의 한계 (이전 학습 연결)
  • JPA 의 등장 (자바 진영 ORM 표준)
  • JPA 동작 위치 (애플리케이션 ↔ JDBC 사이)
  • Spring Data JPA + Querydsl (실무 표준 조합)

7주차 누적 진행

🗂️ Part A — 데이터 모델링과 ORM
  ✅ Phase 1 — SQL JOIN (5)
  ✅ Phase 2 — ORM 패러다임 (2) ← 완주
  ⏭ Phase 3 — JPA 입문 (4)
  ⏭ Phase 4 — JPA 엔티티 매핑 (5)

🔄 Part B — 트랜잭션 추상화의 진화
  ⏭ Phase 5 (2)
  ⏭ Phase 6 (3)
  ⏭ Phase 7 (3)

총: 7/24 Unit

🏆 Phase 2 완주 — ORM 패러다임

profile
Software Developer

0개의 댓글