[멋사 클라우드 5기] Day 23 - Spring Data JPA (1)

2026. 3. 3. 11:42·Learning Log

1. 왜 JPA가 필요한가? — 패러다임 불일치 문제

객체 지향 vs 관계형 데이터베이스

Java는 객체 지향 언어다.
개발자는 현실 세계를 객체(Object) 로 모델링하고, 객체 간의 관계를 참조(Reference) 로 표현한다.

반면, 우리가 데이터를 저장하는 공간인 RDBMS는
데이터를 테이블과 외래 키(Foreign Key) 로 표현한다.

이 두 세계는 근본적으로 사고방식이 다르다.
이것을 패러다임 불일치(Paradigm Mismatch) 라고 한다.

패러다임 불일치의 구체적인 예시

🔴 문제 1: 상속(Inheritance)

Java에서는 클래스 간 상속이 자연스럽다.

// Java: 상속 관계
class Vehicle {
    Long id;
    String name;
}

class Car extends Vehicle {
    int doorCount;
}

하지만 RDBMS에는 상속이라는 개념이 없다.
Vehicle과 Car를 테이블로 표현하려면 다양한 전략이 필요하고, 이를 수동으로 처리하는 건 매우 번거롭다.


🔴 문제 2: 연관관계(Association)

Java에서 객체 간 관계는 참조 로 표현된다.

// Java: 참조로 관계 표현
class Order {
    Member member; // Member 객체를 직접 참조
}

RDBMS에서는 외래 키(FK) 로 관계를 표현한다.

-- SQL: FK로 관계 표현
CREATE TABLE orders (
    id BIGINT,
    member_id BIGINT  -- Member의 PK를 저장
);

Java 객체는 order.getMember() 처럼 객체를 직접 꺼내오지만, SQL은 JOIN 을 통해 데이터를 합쳐야 한다.
이 간극을 개발자가 직접 메워야 한다.


🔴 문제 3: 동일성(Identity)

Java에서 같은 객체인지 비교할 때는 == 또는 equals() 를 쓴다.

Member m1 = memberDAO.findById(1L);
Member m2 = memberDAO.findById(1L);

m1 == m2; // false! 서로 다른 인스턴스

같은 회원(id=1)을 두 번 조회해도, 매번 새로운 객체가 반환되어 == 비교 시 false 가 나온다.
데이터베이스 입장에서 같은 행(row)이지만, Java 입장에서는 다른 객체다.


패러다임 불일치를 직접 해결하면?

JPA 없이 JDBC만으로 위 문제들을 해결하려면, 개발자가 직접 SQL을 작성하고,
ResultSet에서 데이터를 꺼내 객체에 매핑하는 지루하고 반복적인 코드 를 수없이 작성해야 한다.

// JPA 없이 직접 작성해야 하는 코드 예시
public Member findById(Long id) {
    String sql = "SELECT * FROM member WHERE id = ?";
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setLong(1, id);
    ResultSet rs = ps.executeQuery();
    
    Member member = new Member();
    member.setId(rs.getLong("id"));
    member.setName(rs.getString("name"));
    member.setEmail(rs.getString("email"));
    // ... 필드가 많아질수록 코드가 늘어남
    return member;
}

이런 코드를 모든 테이블, 모든 CRUD 작업에 대해 작성해야한다...
ORM 은 바로 이 문제를 해결하기 위해 등장했다.


2. ORM이란 무엇인가?

ORM(Object-Relational Mapping) 은 객체 세계와 관계형 데이터베이스 세계를 자동으로 연결해주는 기술이다.

ORM 프레임워크를 사용하면 개발자는 SQL을 직접 작성하지 않아도 된다.
대신 Java 객체를 조작 하면 ORM이 알아서 적절한 SQL을 생성하고 실행한다.

개발자 → Java 객체 조작 → ORM → SQL 자동 생성 → RDBMS

ORM의 핵심 아이디어는 "Java 클래스와 DB 테이블을 연결(매핑)하자" 는 것이다.

Member 클래스  ↔  MEMBER 테이블
id 필드        ↔  ID 컬럼
name 필드      ↔  NAME 컬럼

3. JPA란 무엇인가?

JPA = Java ORM의 표준 명세

JPA(Java Persistence API, 혹은 Jakarta Persistence API)는
Java 진영에서 ORM 기술을 사용하기 위한 표준 인터페이스(Interface) 모음 이다.

핵심 포인트: JPA는 구현체(Implementation)가 아니라 명세(Specification) 다.

마치 JDBC가 데이터베이스 연결을 위한 표준 인터페이스이고, 실제 구현은 MySQL Driver, PostgreSQL Driver 등이 담당하는 것처럼,
JPA는 ORM의 표준 인터페이스이고, 실제 구현은 Hibernate, EclipseLink, OpenJPA 등이 담당한다.

JPA (인터페이스/명세)
    ├── Hibernate (가장 많이 쓰이는 구현체)
    ├── EclipseLink
    └── OpenJPA

JPA를 사용한다는 것은 JPA가 정의한 인터페이스를 통해 ORM 기능을 사용한다는 뜻이다.
구현체를 Hibernate에서 다른 것으로 바꿔도 코드가 크게 변하지 않는 이유가 바로 이 덕분이다.

JPA의 핵심 역할

JPA는 크게 세 가지 역할을 한다.

  1. CRUD SQL 자동 생성 — persist(), find(), remove() 등의 메서드로 SQL을 자동 생성한다.
  2. 결과를 자동으로 객체에 매핑 — SQL 실행 결과를 Java 객체로 자동 변환한다.
  3. 패러다임 불일치 해결 — 상속, 연관관계, 객체 그래프 탐색 등을 처리한다.

4. 전체 구조 이해하기

아래 그림이 나타내는 구조를 층별로 이해해보자.

┌──────────────────────────────────────┐
│        Spring Boot Application       │
└──────────────┬───────────────────────┘
               │ ↕
┌──────────────▼───────────────────────┐
│   ┌──────────────────────────────┐   │
│   │      Spring Data JPA         │   │  ← 편의 레이어 (Repository 자동 구현)
│   └──────────────────────────────┘   │
│   ┌──────────────────────────────┐   │
│   │    Java Persistence API      │   │  ← ORM 표준 명세 (인터페이스)
│   └──────────────────────────────┘   │
│   ┌──────────────────────────────┐   │
│   │         Hibernate            │   │  ← JPA 구현체 (실제 ORM 동작)
│   └──────────────────────────────┘   │
│   ┌──────────────────────────────┐   │
│   │           JDBC               │   │  ← DB와의 저수준 통신 담당
│   └──────────────────────────────┘   │
└──────────────┬───────────────────────┘
               │ ↕
        ┌──────▼──────┐
        │    RDBMS    │
        └─────────────┘

JDBC (Java Database Connectivity)

JDBC 는 Java 프로그램이 데이터베이스와 통신하기 위한 가장 기본적인 API다.
SQL 작성, Connection 열기, PreparedStatement 실행, ResultSet에서 데이터 꺼내기와 같은 모든 저수준 작업을 담당한다.

모든 Java ORM은 내부적으로 JDBC를 사용한다.
JPA, Hibernate 모두 최종적으로는 JDBC를 통해 DB와 통신한다.

Hibernate

Hibernate 는 JPA 명세를 실제로 구현한 ORM 프레임워크다.
JPA가 정의한 인터페이스의 실제 로직을 담고 있으며,
SQL 자동 생성, 캐싱, 영속성 컨텍스트 관리 등의 핵심 기능을 제공한다.

Java Persistence API (JPA)

Hibernate 같은 구현체들이 공통적으로 따르는 표준 인터페이스 집합 이다.
EntityManager, @Entity, @Table 등이 JPA가 정의한 표준 요소들이다.

Spring Data JPA

JPA를 더 편리하게 사용할 수 있도록 Spring 팀이 추가로 감싼 편의 레이어 다.
Repository 인터페이스만 정의하면 기본 CRUD를 자동으로 구현해주는 등, 반복 코드를 크게 줄여준다.


5. Hibernate란?

Hibernate는 현재 사실상 Java ORM의 표준 구현체로 자리 잡은 오픈소스 프레임워크다.
JPA 명세의 거의 모든 기능을 구현하며, 추가적인 고급 기능도 제공한다.

Spring Boot에서 spring-boot-starter-data-jpa 의존성을 추가하면, 기본적으로 Hibernate가 JPA 구현체로 자동 설정된다.

Hibernate의 핵심 기능

기능 설명
자동 SQL 생성 객체 조작에 따라 INSERT/UPDATE/DELETE/SELECT SQL을 자동 생성
영속성 컨텍스트 객체의 상태를 관리하는 1차 캐시 영역
지연 로딩 (Lazy Loading) 연관된 객체를 실제로 사용할 때까지 DB 조회를 미룸
HQL / JPQL 객체 지향적인 쿼리 언어 지원
캐싱 (2차 캐시) 성능 향상을 위한 캐시 기능

6. Spring Data JPA란 무엇인가?

JPA만 사용할 때의 불편함

JPA를 직접 사용하면 EntityManager 를 주입받아 매번 비슷한 CRUD 코드를 작성해야 한다.

// JPA EntityManager를 직접 사용하는 경우
@Repository
public class MemberRepository {
    
    @PersistenceContext
    private EntityManager em;
    
    public void save(Member member) {
        em.persist(member);
    }
    
    public Member findById(Long id) {
        return em.find(Member.class, id);
    }
    
    public List<Member> findAll() {
        return em.createQuery("SELECT m FROM Member m", Member.class).getResultList();
    }
    
    public void delete(Member member) {
        em.remove(member);
    }
}

save, findById, findAll, delete 같은 기본 CRUD는 모든 엔티티에 반복적으로 등장한다.
Member 뿐만 아니라 Order, Product, Review 등 엔티티마다 동일한 코드를 작성해야 한다면 매우 비효율적이다.

Spring Data JPA의 해결책

Spring Data JPA 는 이 반복을 제거한다.
개발자가 인터페이스만 선언 하면, Spring이 런타임에 자동으로 구현체를 만들어준다.

// Spring Data JPA 사용 — 이게 전부다!
public interface MemberRepository extends JpaRepository<Member, Long> {
    // 기본 CRUD는 자동으로 제공됨
    // 필요한 쿼리만 메서드 이름으로 선언하면 됨
    List<Member> findByName(String name);
    Optional<Member> findByEmail(String email);
}

JpaRepository<Member, Long> 을 상속받는 것만으로
save(), findById(), findAll(), delete() 등 수십 개의 메서드가 자동으로 생긴다.

메서드 이름으로 쿼리 생성 (Query Method)

Spring Data JPA의 강력한 기능 중 하나는 메서드 이름만으로 쿼리를 자동 생성 해주는 것이다.

// 메서드 이름 → 자동 생성되는 SQL
findByName(String name)
// → SELECT * FROM member WHERE name = ?

findByAgeGreaterThan(int age)
// → SELECT * FROM member WHERE age > ?

findByNameAndEmail(String name, String email)
// → SELECT * FROM member WHERE name = ? AND email = ?

findByNameContaining(String keyword)
// → SELECT * FROM member WHERE name LIKE '%keyword%'

7. Entity란 무엇인가?

Entity의 정의

Entity(엔티티) 는 데이터베이스 테이블과 매핑되는 Java 클래스다.
JPA는 Entity 클래스를 보고 어떤 테이블에 어떤 컬럼으로 저장할지 파악한다.

import jakarta.persistence.*;

@Entity                          // 이 클래스가 JPA Entity임을 선언
@Table(name = "member")          // 매핑할 테이블 이름 (생략하면 클래스 이름 사용)
public class Member {
    
    @Id                          // 기본 키(PK) 필드임을 선언
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // PK 자동 생성 전략
    private Long id;
    
    @Column(name = "member_name", nullable = false, length = 50)
    private String name;         // DB 컬럼과 매핑
    
    @Column(unique = true)
    private String email;
    
    protected Member() {}        // JPA는 기본 생성자가 반드시 필요 (public 또는 protected)
    
    public Member(String name, String email) {
        this.name = name;
        this.email = email;
    }
    
    // Getter/Setter...
}

주요 JPA 어노테이션

annotation 역할
@Entity 이 클래스가 JPA 관리 대상 엔티티임을 선언
@Table 매핑할 DB 테이블 지정
@Id 기본 키(Primary Key) 필드 지정
@GeneratedValue PK 자동 생성 전략 설정
@Column 컬럼 세부 설정 (이름, null 허용 여부, 길이 등)
@ManyToOne, @OneToMany 연관관계 매핑
@Transient 이 필드는 DB에 저장하지 않음

@GeneratedValue 전략

PK를 자동으로 생성할 때 4가지 전략이 있다.

전략 설명 주로 사용하는 DB
IDENTITY DB가 PK 자동 증가 MySQL, MariaDB
SEQUENCE DB 시퀀스 사용 Oracle, PostgreSQL
TABLE 별도 키 생성 테이블 사용 모든 DB (잘 안 씀)
AUTO DB에 따라 자동 선택 기본값

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

JPA를 이해하는 데 있어 가장 중요한 핵심이다.
영속성 컨텍스트는 JPA의 1차 캐시, 쓰기 지연, 변경 감지를 위해 필수적이기 때문이다.

영속성 컨텍스트란?

영속성 컨텍스트(Persistence Context) 는 엔티티를 영구 저장하는 환경이다.
쉽게 말하면 JPA가 관리하는 엔티티들을 담아두는 메모리 공간이다.

🗃️ 영속성 컨텍스트 = JPA가 관리하는 엔티티 저장소 (메모리 위)

이 컨텍스트는 EntityManager 를 통해 접근한다.
하나의 EntityManager는 하나의 영속성 컨텍스트를 가진다.

EntityManager ↔ Persistence Context ↔ DB

EntityManager

EntityManager 는 영속성 컨텍스트를 통해 엔티티를 관리하는 JPA의 핵심 인터페이스다.
CRUD 작업, 트랜잭션, 쿼리 실행 등의 작업을 담당한다.

Spring Data JPA를 쓰면 EntityManager를 직접 다룰 일은 거의 없지만, 내부적으로는 항상 EntityManager가 동작한다.

트랜잭션과 영속성 컨텍스트

Spring에서 영속성 컨텍스트의 생명주기는 기본적으로 트랜잭션(Transaction)과 함께한다.

  • 트랜잭션 시작 → 영속성 컨텍스트 생성
  • 트랜잭션 커밋(commit) → DB에 반영 + 영속성 컨텍스트 종료
  • 트랜잭션 롤백(rollback) → 변경 취소 + 영속성 컨텍스트 종료

트랜잭션(Transaction) 이란? 데이터베이스의 작업 단위다. "모두 성공하거나, 하나라도 실패하면 전부 취소"하는 원자성(Atomicity)을 보장한다. @Transactional 어노테이션으로 선언한다.


9. Entity의 생명주기 (Life Cycle)

엔티티는 영속성 컨텍스트와의 관계에 따라 4가지 상태를 가진다.

        new Member()
             │
             ▼
     ┌─────────────────┐
     │  비영속 상태       │  ← JPA가 모르는 상태 (일반 Java 객체)
     │  (New/Transient)│
     └───────┬─────────┘
             │ em.persist(member)
             ▼
     ┌───────────────┐
     │   영속 상태     │  ← JPA가 관리하는 상태 (영속성 컨텍스트에 저장됨)
     │  (Managed)    │◄──────────────────────────┐
     └─────────┬─────┘                           │
               │                           em.merge(member)
    em.detach()│                                 │
    em.clear() │                                 │
               ▼                                 │
     ┌───────────────┐                           │
     │  준영속 상태     │  ← 한때 영속이었지만 분리됨     │
     │  (Detached)   │ ──────────────────────────┘
     └─────────┬─────┘
               │
    em.remove()│
               ▼
     ┌───────────────┐
     │   삭제 상태     │  ← DB에서 삭제 예정
     │  (Removed)    │
     └───────────────┘

각 상태 설명

1. 비영속 (New / Transient)

엔티티 객체를 방금 new 로 생성한 상태다.
JPA와 전혀 관계가 없다. 일반 Java 객체와 동일하다.

Member member = new Member("홍길동", "hong@email.com");
// 이 시점의 member는 비영속 상태
// JPA는 이 객체의 존재를 모른다

2. 영속 (Managed)

엔티티가 영속성 컨텍스트에 저장된 상태다.
JPA가 이 객체를 추적하고 관리 한다.
em.persist() 를 호출하거나, em.find() 로 DB에서 조회한 엔티티는 영속 상태가 된다.

em.persist(member); // 영속 상태로 전환
// 또는
Member found = em.find(Member.class, 1L); // DB에서 조회 → 영속 상태

중요: em.persist() 를 호출한다고 즉시 DB에 INSERT SQL이 실행되는 게 아니다.
영속성 컨텍스트에 등록될 뿐이며, 실제 SQL은 트랜잭션 커밋 시점에 실행된다. (→ 쓰기 지연 참고)

3. 준영속 (Detached)

영속성 컨텍스트에서 분리된 상태다. 한때 영속 상태였지만, JPA의 관리에서 벗어난 상태다.
변경 감지, 지연 로딩 등의 JPA 기능이 동작하지 않는다.

em.detach(member); // 특정 엔티티만 분리
em.clear();        // 영속성 컨텍스트 전체 초기화
em.close();        // EntityManager 종료

트랜잭션이 끝난 후, Controller나 View 계층에서 엔티티를 사용할 때 이 상태가 된다.

4. 삭제 (Removed)

엔티티를 삭제 요청한 상태다. 트랜잭션 커밋 시점에 DELETE SQL이 실행된다.

em.remove(member); // 삭제 상태로 전환

10. 1차 캐시 (First-Level Cache)

1차 캐시란?

영속성 컨텍스트 내부에는 Map 형태의 저장소가 있다. 이것이 바로 1차 캐시 다.
Key는 엔티티의 @Id 값(PK), Value는 엔티티 객체다.

영속성 컨텍스트 (1차 캐시)
┌─────────────────────────────────┐
│  Key (PK)  │  Value (Entity)    │
├────────────┼────────────────────┤
│  1L        │  Member{id=1, ...} │
│  2L        │  Member{id=2, ...} │
└─────────────────────────────────┘

1차 캐시의 동작

em.find() 로 엔티티를 조회할 때, JPA는 먼저 1차 캐시를 확인 한다.

// 1차 캐시 동작 예시
Member m1 = em.find(Member.class, 1L); // DB 조회 → 1차 캐시에 저장
Member m2 = em.find(Member.class, 1L); // 1차 캐시에서 반환 (DB 조회 없음!)

m1 == m2; // true! 같은 인스턴스

첫 번째 find(): 1차 캐시에 없음 → DB에 SELECT SQL 실행 → 결과를 1차 캐시에 저장 → 반환

두 번째 find(): 1차 캐시에 있음 → DB 조회 없이 캐시에서 바로 반환

1차 캐시의 장점

  1. 불필요한 DB 조회 감소 — 동일한 트랜잭션 내에서 같은 엔티티를 여러 번 조회해도 SQL은 한 번만 실행된다.
  2. 동일성 보장 — 같은 PK로 조회한 엔티티는 항상 같은 인스턴스다 (== 비교 가능).

1차 캐시의 범위

1차 캐시는 트랜잭션 단위로 존재 한다. 트랜잭션이 끝나면 영속성 컨텍스트가 종료되고 1차 캐시도 사라진다.
애플리케이션 전체에서 공유되는 캐시가 아님을 주의해야 한다.

2차 캐시(Second-Level Cache) 란? Hibernate는 여러 트랜잭션에 걸쳐 공유되는 2차 캐시도 지원한다.
애플리케이션 레벨에서 공유되는 캐시로, 별도 설정이 필요하다. (예: EhCache, Redis 연동)


11. 쓰기 지연 (Write-Behind)

쓰기 지연이란?

영속성 컨텍스트는 내부에 쓰기 지연 SQL 저장소(ActionQueue) 를 가지고 있다.
엔티티를 persist() 하거나 변경해도, SQL이 즉시 DB로 전송되지 않는다.
대신, SQL을 이 저장소에 모아두었다가 트랜잭션 커밋 직전에 한꺼번에 DB로 전송 한다.

이 동작을 쓰기 지연(Transactional Write-Behind) 이라고 한다.

쓰기 지연의 동작 과정

@Transactional
public void saveMembersExample() {
    Member m1 = new Member("홍길동", "hong@email.com");
    Member m2 = new Member("김철수", "kim@email.com");
    
    em.persist(m1); // SQL 저장소에 INSERT 쌓기 (DB 전송 X)
    em.persist(m2); // SQL 저장소에 INSERT 쌓기 (DB 전송 X)
    
    // 이 시점까지 DB에는 아무 변화가 없음
    
} // 메서드 종료 → 트랜잭션 커밋 → flush() 실행 → SQL 일괄 전송 → DB 반영
1. em.persist(m1) 호출
   → 영속성 컨텍스트에 m1 저장
   → SQL 저장소: [INSERT INTO member VALUES (m1)]
   → DB: 변화 없음

2. em.persist(m2) 호출
   → 영속성 컨텍스트에 m2 저장
   → SQL 저장소: [INSERT INTO member VALUES (m1), INSERT INTO member VALUES (m2)]
   → DB: 변화 없음

3. 트랜잭션 커밋
   → flush() 실행: 저장소의 SQL 일괄 DB 전송
   → DB: m1, m2 저장 완료

flush()란?

flush() 는 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 작업이다. 트랜잭션 커밋 시 자동으로 호출된다.

flush()를 한다고 트랜잭션이 커밋되는 건 아니다. DB에 SQL을 전송할 뿐이며, 커밋은 별개다.

쓰기 지연의 장점

  • 성능 최적화: 여러 SQL을 하나의 배치(Batch)로 전송할 수 있다.
  • DB와의 통신 횟수 감소: 10번 persist해도 DB 요청은 한 번.
  • 불필요한 DB 부하 제거: 취소될 수 있는 작업을 미리 DB에 보내지 않아도 된다.

12. 변경 감지 (Dirty Checking)

변경 감지란?

JPA의 가장 마법 같은 기능 중 하나다.
영속 상태의 엔티티를 수정하기만 해도, 트랜잭션 커밋 시점에 JPA가 자동으로 UPDATE SQL을 실행 한다.
개발자가 직접 save() 나 update() 를 호출할 필요가 없다.

@Transactional
public void updateMember(Long id) {
    Member member = em.find(Member.class, id); // 영속 상태로 조회
    
    member.setName("새이름"); // 그냥 setter 호출만 해도...
    
    // em.persist(member); // 이 코드가 없어도!
    // em.update(member);  // 이런 메서드도 없음!
    
} // 트랜잭션 커밋 → JPA가 자동으로 UPDATE SQL 실행

변경 감지의 동작 원리

JPA는 어떻게 변경을 감지할까? 바로 스냅샷(Snapshot) 을 활용한다.

  1. 영속화 시점: 엔티티가 영속성 컨텍스트에 들어올 때, JPA는 해당 엔티티의 초기 상태를 복사해 스냅샷으로 저장 한다.
  2. 커밋 시점: flush()가 호출될 때, JPA는 현재 엔티티 상태와 스냅샷을 비교한다.
  3. 차이 발생 시: 변경된 필드가 있으면 UPDATE SQL을 자동으로 생성해 실행한다.
영속성 컨텍스트
┌────────────────────────────────────────────┐
│ 1차 캐시                                     │
│  Member{id=1, name="홍길동", email="hong@"}  │ ← 현재 상태
│                                            │
│ 스냅샷                                       │
│  Member{id=1, name="홍길동", email="hong@"}  │ ← 최초 상태 (복사본)
└────────────────────────────────────────────┘

member.setName("김길동") 호출 후:

┌────────────────────────────────────────────┐
│ 1차 캐시                                     │
│  Member{id=1, name="김길동", email="hong@"}  │ ← 현재 상태 (변경됨)
│                                            │
│ 스냅샷                                       │
│  Member{id=1, name="홍길동", email="hong@"}  │ ← 최초 상태 (그대로)
└────────────────────────────────────────────┘
      ↓ flush() 시 비교
UPDATE member SET name='김길동' WHERE id=1

변경 감지 주의사항

변경 감지는 영속 상태의 엔티티에만 동작 한다. 트랜잭션 밖에서 엔티티를 수정하거나,
준영속(Detached) 상태의 엔티티를 수정해도 UPDATE SQL은 실행되지 않는다.

// ❌ 변경 감지 동작 안 함 (트랜잭션 없음)
Member member = memberRepository.findById(1L).get();
member.setName("새이름"); // 준영속 상태 → DB 반영 안 됨

// ✅ 변경 감지 동작 (트랜잭션 있음)
@Transactional
public void update(Long id) {
    Member member = memberRepository.findById(id).get(); // 영속 상태
    member.setName("새이름"); // → 커밋 시 UPDATE 자동 실행
}

13. Spring Data JPA 실전 사용법

의존성 추가

<!-- Maven -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
// Gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'

application.yml 설정

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update       # 애플리케이션 시작 시 테이블 자동 생성/수정
    show-sql: true            # 실행되는 SQL 로그 출력
    properties:
      hibernate:
        format_sql: true      # SQL 로그를 보기 좋게 포맷

ddl-auto 옵션 설명

  • create: 시작 시 테이블 삭제 후 재생성 (데이터 유실 위험)
  • update: 변경된 스키마만 반영 (기존 데이터 보존)
  • validate: 스키마 검증만 (변경 없음)
  • none: 아무것도 안 함
  • 운영 환경에서는 반드시 validate 또는 none 사용 권장

전체 예시 코드

// 1. Entity 정의
@Entity
@Table(name = "member")
@Getter @Setter
@NoArgsConstructor
public class Member {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(unique = true, nullable = false)
    private String email;
    
    public Member(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

// 2. Repository 정의
public interface MemberRepository extends JpaRepository<Member, Long> {
    Optional<Member> findByEmail(String email);
    List<Member> findByNameContaining(String keyword);
    
    // JPQL 직접 작성도 가능
    @Query("SELECT m FROM Member m WHERE m.name = :name AND m.email = :email")
    Optional<Member> findByNameAndEmail(@Param("name") String name, 
                                        @Param("email") String email);
}

// 3. Service 계층 — 트랜잭션 관리
@Service
@RequiredArgsConstructor
public class MemberService {
    
    private final MemberRepository memberRepository;
    
    @Transactional
    public Member createMember(String name, String email) {
        Member member = new Member(name, email);
        return memberRepository.save(member); // persist → 영속화
    }
    
    @Transactional(readOnly = true)  // 읽기 전용 트랜잭션 (성능 최적화)
    public Member getMember(Long id) {
        return memberRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다."));
    }
    
    @Transactional
    public void updateMemberName(Long id, String newName) {
        Member member = memberRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다."));
        member.setName(newName); // 변경 감지 → 자동 UPDATE
        // save() 호출 불필요!
    }
    
    @Transactional
    public void deleteMember(Long id) {
        memberRepository.deleteById(id);
    }
}

JPQL이란?

JPQL(Java Persistence Query Language) 은 JPA에서 사용하는 객체 지향 쿼리 언어다.
SQL과 비슷하지만, 테이블과 컬럼이 아닌 엔티티 클래스와 필드명을 사용 한다.

-- SQL (테이블/컬럼 대상)
SELECT * FROM member WHERE name = 'hong'

-- JPQL (엔티티/필드 대상)
SELECT m FROM Member m WHERE m.name = 'hong'

14. 정리 및 흐름 요약

전체 흐름 한 눈에 보기

[Spring Boot Application]
        │
        │ Repository 메서드 호출
        ▼
[Spring Data JPA]
  JpaRepository 구현체 자동 생성
        │
        │ EntityManager 호출
        ▼
[JPA / Hibernate]
  영속성 컨텍스트 관리
  ┌─────────────────────────────┐
  │ 1차 캐시                      │
  │ 쓰기 지연 SQL 저장소            │
  │ 스냅샷 (변경 감지용)             │
  └─────────────────────────────┘
        │
        │ 트랜잭션 커밋 → flush()
        ▼
[JDBC]
  SQL 실행 (커넥션 풀 관리)
        │
        ▼
[RDBMS]
  실제 데이터 저장

'Learning Log' 카테고리의 다른 글

[멋사 클라우드 5기] Day 25 - Spring Data JPA (3)  (0) 2026.03.05
[멋사 클라우드 5기] Day 24 - Spring Data JPA (2)  (0) 2026.03.04
[멋사 클라우드 5기] Day 22 - SpringBoot 그리고 웹 서버  (0) 2026.02.27
[멋사 클라우드 5기] Day 21 - 제어의 역전(IoC)과 의존성 주입(DI)  (0) 2026.02.26
[멋사 클라우드 5기] Day 20 - JavaScript의 비동기  (0) 2026.02.25
'Learning Log' 카테고리의 다른 글
  • [멋사 클라우드 5기] Day 25 - Spring Data JPA (3)
  • [멋사 클라우드 5기] Day 24 - Spring Data JPA (2)
  • [멋사 클라우드 5기] Day 22 - SpringBoot 그리고 웹 서버
  • [멋사 클라우드 5기] Day 21 - 제어의 역전(IoC)과 의존성 주입(DI)
allluck777
allluck777
allluck777
    • 분류 전체보기 (41)
      • AWS (0)
      • Network (0)
      • Linux (0)
      • Docker (0)
      • Project (4)
        • CloudNote (4)
      • Learning Log (34)
      • Lecture (3)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (3)
  • 전체
    오늘
    어제
  • hELLO· Designed By정상우.v4.10.6
allluck777
[멋사 클라우드 5기] Day 23 - Spring Data JPA (1)
상단으로

티스토리툴바