Backend Development/SPRING

JPA & Spring Data JPA

foreverWon 2025. 7. 19. 18:18
목차

    1. 스프링 데이터 액세스 기술

    • SQL 중심 기술
      • MyBatis, Spring JDBC
      • 스키마 변경 시 SQL 전수 수정
    • 객체 중심 기술(ORM)
      • 객체(Object)와 DB 테이블 매핑을 통해 엔티티 클래스 객체 안에 포함된 정보를 테이블에 저장하는 기술
      • 종류: JPA, Spring Data JPA
      • 특징
        • 객체(Entity) 단위 → 객체와 테이블 간 자동 매핑
        • SQL을 직접 작성하지 않아도 대부분의 CRUD 작업 가능
        • 영속성 컨텍스트를 통해 객체 상태 관리
        • 객체 상태 변화에 따라 DB 반영 → DDD(도메인 주도 설계)에 적합
        • DB 종속성 감소
        • 연관관계 매핑, Fetch 전략 등 다양한 기능 제공
      • 대표 기술
        • JPA: Java ORM 표준 명세서 → 구현체(Hibernate) 필요
        • Spring Data JPQ

    2. JPA(Java Persistence API)

    • Java 진영에서 사용하는 ORM 기술의 표준 명세 → 구현체 필요
    • Java 객체와 RDB의 데이터를 매핑하기 위한 표준 기술
    • SQL 없이 자바 객체(Entity)를 통해 데이터 조작
    • 비즈니스 로직과 DB 쿼리 분리
    • 특징
      • 표준 API
      • 생산성 향상: SQL을 직접 작성하지 않고도 CRUD 처리 가능
      • 유지보수 용이: 객체 기반 코드 작성, SQL 분리로 코드 가독성 향상
      • DBMS 독립성
      • ORM 기능: 객체와 DB 테이블 자동 매핑

    2-1) Hibernate

    • JPA에 정의해 둔 인터페이스를 구현한 구현체
    • 동작 구조[그림] 데이터 액세스 계층에서의 JPA 위치
      • Application → JPA Interface → Hibernate → JDBC → DB

    데이터 액세스 계층에서의 JPA 위치

    2-2) JPA 동작 방식

    • 주요 개념
      • Entity
        • DB 테이블과 매핑되는 자바 클래스
        • @Entity, @Id, @GeneratedValue
        • @Entity @Table(name = "members") public class Member { @Id @GeneratedValue private Long id; }
      • EntityManager
        • JPA 핵심 인터페이스
        • Entity의 생명주기 관리, CRUD, JPQL 실행 등 담당
          • JPQL(Java Persistence Query Language)
            • SQL과 유사하지만 Entity 대상 질의
            • 객체(Entity) 중심 언어
      • Persistence Context(영속성 컨텍스트)
        • 엔티티 객체 정보를 영속성 컨텍스트에 보관
        • 구조
          • 1차 캐시 영역
          • 쓰기 지연 SQL 저장소
      • Transaction
    • 동작 단계
      1. Entity 생성 → persist()로 영속 상태 진입(DB 저장 X)
      2. 트랜잭션 커밋 시 실제 SQL 실행 → DB 반영
      3. 조회 시 1차 캐시(영속성 컨텍스트) 우선 검색

    3. Spring Data JPA

    • Spring Framework에서 제공하는 데이터 접근 기술
    • JPA 기반으로 반복적인 CRUD 코드 생략과 표준화된 인터페이스를 통해 생산성과 유지보수성을 극대화
    • 역할
      • Repository 패턴을 통한 데이터 접근 표준화
      • JPA 위에 얹혀 동작 (DB 코드가 간결한 이유)
    • 동작 구조
      1. Repository Interface 작성 (JpaRepository 상속)
        • 인터페이스만 작성
        • 구현 필요 없음: Spring이 알아서 작성해줌
      2. Spring Boot 실행 시 Spring Data JPA가 내부적으로 Proxy 객체 생성 (SimpleJpaRepository)
      3. 메서드 이름 분석 → JPQL 생성
      4. EntityManager로 JPA에게 위임하여 DB 조회
    • 주요 기능
      • JpaRepository 상속: CRUD 메서드
      • Query Method: 메서드 이름으로 쿼리 자동 생성(findByEmail 등)
      • @Query: JPQL 작성 가능
      • Native Query: 실제 DB SQL 사용 가능
      • 페이징/정렬: Pageable, Sort 인터페이스 제공
      • Projection: 일부 컬럼만 조회 가능
    • 실무 TIP
      • JpaRepository 상속 + Query Method 조합으로 대부분 해결 가능
      • 복잡한 조건은 @Query 사용
      • 성능 튜닝 필요 시 Native Query 사용
      • 페이징 처리 시 Pageable, Sort 적극 활용

    4. Entity 설계

    4-1) 엔티티 - 테이블 Mapping

    • @Entity(name = "...")
      • 엔티티 이름 설정
    • @Table(name = “…”)
      • 테이블 이름 설정
      • 옵션 설정이며 추가하지 않을 경우 클래스 이름을 테이블 이름으로 사용

    4-2) 기본키 매핑

    • @Id
    • JPA의 기본키 생성 전략 (@GeneratedValue(strategy = GenerationType.*)
      • IDENTITY: DB에 위임
      • SEQUENCE: DB에서 제공하는 시퀀스 사용
      • TABLE: 별도의 키 생성 테이블 사용

    4-3) 필드 - 열 Mapping

    • @Column
      • nullable = true
      • updatable = true
      • unique = false
      • length = 255
    • @Transient
      • 테이블 열과 매핑하지 않음
      • 임시 데이터를 메머리에 사용하기 위한 용도
    • @Enumerated(EnumType.*)
      • Enum 타입과 매핑
      • ORDINAL: enum 순서를 나타내는 숫자 저장
      • STRING: enum 이름 저장

    4-4) 권장 사용 방법

    • @Entity: 클래스 레벨에 추가하면 JPA의 관리대상 엔티티가 됨
    • @Table: 앤티티와 매핑할 테이블 지정
    • @Entity + @Id: 필수 추가
    • 기본키 생성 전략은 SEQUENCE 전략 사용: @GeneratedValue(strategy = GenerationType.SEQUENCE
    • @Column: 필수는 아니지만 무조건 추가(에러 방지)
    • LocalDate, LocalDateTime 타입은 @Temporal 생략 가능
    • @Transient: JPA가 테이블 열과 매핑 X
    • @EnumType.SRTING 사용 추천

    5. Entity 연관 관계 매핑

    • 엔티티 클래스는 객체 참조를 통해 관계를 맺음
    1. 단방향 연관 관계
      • 한쪽 클래스만 다른 쪽 클래스의 참조 정보를 가지고 있음
    2. 양방향 연관 관계
      • 양쪽 클래스가 서로의 참조 정보를 가지고 있음
    3. 일대다 단방향 연관 관계
      • 잘 사용 X
      • 다대일 단방향 매핑 후, 필요한 경우 일대다 단방형 매핑 추가가 일반적
    4. 다대일 연관 관계
      • N에 해당하는 클래스가 1에 해당하는 객체 참조하는 관계
      • 테이블 간의 관계와 비슷

    6. JpaRepository 인터페이스

    • CRUD + 페이징 + 정렬
    • 주요 메서드 
      메서드 기능
      save() 저장 또는 수정 (PK 유무에 따라 구분)
      findById() ID로 단건 조회
      findAll() 전체 조회
      count() 전체 개수 조회
      delete() 단건 삭제
      deleteAll() 전체 삭제
      existsById() 존재 여부 확인
      findAll(Pageable) 페이징 처리된 조회
      findAll(Sort) 정렬된 조회
    • 쿼리 메서드 작성 규칙
      키워드 의미
      findBy 조회
      countBy 개수 조회
      deleteBy 삭제
      existsBy 존재 여부
      TopN 상위 N개
      First 첫 번째
    • 실무 TIP
      • JpaRepository만으로 단순 CRUD 충분
      • 복잡 쿼리는 @Query 사용
      • 페이징, 정렬은 Pageable, Sort 적극 활용
      • Optional 반환으로 NPE 방지

    7. 페이징과 정렬

    7-1) Pageable 인터페이스

    • 페이징을 위한 정보를 담는 인터페이스
    • 페이지 번호, 페이지 크기, 정렬 정보 포함
    • 주요 메서드
      메서드 설명
      getPageNumber() 현재 페이지 번호 (0부터 시작)
      getPageSize() 한 페이지당 데이터 수
      getSort() 정렬 정보 반환
      next() 다음 페이지 Pageable 반환
      previousOrFirst() 이전 페이지 또는 첫 페이지 Pageable 반환
    • 예시 코드
    // "username 필드를 기준으로 내림차순 정렬된 데이터 중 
    // 0번 페이지(첫 번째 페이지)에서 10개 데이터를 가져와라"
    Pageable pageable = PageRequest.of(0, 10, Sort.by("username").descending());
    
    // PageRequest.of(조회할 페이지 번호(0부터 시작), 
    //                한 페이지에 가져올 데이터 수(limit 역할),
    //                정렬 기준 필드와 정렬 방향)

    7-2) Sort 인터페이스

    • 정렬 조건을 명시적으로 설정하는 인터페이스
    • 메서드
      • Sort.by(): 오름차순 기본, 여러 필드 정렬 가능
      • Order.asc()
      • Oser.desc()

    7-3) Page vs Slice

    구분 Page Slice
    반환값 전체 count 포함 count 쿼리 없음 (다음 여부만 확인)
    구성 content, totalPages, totalElements, number content, hasNext(), number
    특징 전체 페이지 계산 (DB 비용 ↑) 현재 페이지와 다음 여부만 (성능 ↑)
    용도 관리자 페이지, 검색 페이지 등 모바일 무한 스크롤, 더보기 버튼 등
    • Page
    Page<Member> page = memberRepository.findAll(pageable);
    
    // 페이지 내부 데이터
    List<Member> content = page.getContent();  // 실제 데이터 목록
    int totalPages = page.getTotalPages();     // 총 페이지 수
    long totalElements = page.getTotalElements(); // 전체 데이터 수
    boolean hasNext = page.hasNext();          // 다음 페이지 여부
    • Slice
    Slice<Member> slice = memberRepository.findByAgeGreaterThan(20, pageable);
    
    // Slice 내부 데이터
    List<Member> content = slice.getContent(); // 실제 데이터 목록
    boolean hasNext = slice.hasNext();         // 다음 페이지 존재 여부

    8. JPA 활용 시 주의사항

    8-1) N+1 문제

    • ORM(JPA, Hiberate) 환경의 대표적인 문제
    • 1반의 쿼리로 N개의 데이터를 조회한 후, 각 데이터의 연관된 데이터를 추가 조회하면서 총 N+1번의 쿼리 발생
    • 발생 원인: LAZY 전략으로 연관 관계가 설정된 엔티티를 조회할 때, 각각 개별적으로 추가 조회 발생
    💡 실무 TIP
    - JPA는 연관 관계 기본 Fetch 전략이 Lazy
    - 컬렉션(List)의 Lazy 로딩은 특히 조심 

    해결 1: Fetch Join

    • 연관된 엔티티 한번에 조회
    • 특징 
      특징 내용
      동작 방식 JPQL에서 JOIN FETCH 명시
      즉시 로딩 한 번의 쿼리로 연관 객체까지 즉시 로드
      컬렉션 주의 DISTINCT 사용 권장 (중복 데이터 방지)
    💡 실무 TIP
    - OneToMany Fetch Join 시 중복 발생 → DISTINCT 사용 권장
    - 페이징 불가

    해결 2: EntityGraph

    • 쿼리를 작성하지 않고 fetch 전략 재정의
    • @EntityGraph(attributePaths = {”…”})

    8-2) JPA의 로딩 전략

    • 즉시 로딩(EAGER)
      • 부모 엔티티를 조회할 때, 연관된 자식 엔티티를 즉시 조회
      • 특징
        • 성능 저하 유발
        • N+1 발생 가능
    • 지연 로딩
      • 실제로 사용할 때 쿼리 실행

    8-3) 영속성 전이(Cascade)

    • 부모를 저장/삭제하면 자식까지 함께 저장/삭제
    • 트랜잭션 단위의 일관성 유지 가능
    • 종류
    옵션  설명
    PERSIST 부모 저장 시 자식 자동 저장
    MERGE 부모 병합 시 자식 자동 병합
    REMOVE 부모 삭제 시 자식 자동 삭제
    REFRESH 부모 초기화 시 자식도 초기화
    DETACH 부모 detach 시 자식도 detach

    8-4) 고아 객체 제거(orphanRemoval)

    • “컬렉션”에서 엔티티가 제거되면 자동으로 DB에서 삭제해주는 기능
    • 필요 없어진 데이터 정리
    • 외래키가 위치한 반대에 위치하며, 그때 참조되지 않은 객체가 고아객체
    • 영속성 관계 안에서만 적용
    @OneToMany(mappedBy = “parent”, orphanRemoval = true)
    private List<Child> childeren = new ArrayList<>();
    
    💡 실무 TIP
    - 부모에 속한 자식이 따로 존재 의미가 없을 때 사용
    - 예:
    - 게시판 - 댓글
    주문 - 주문상세

    9. 회고: 인상 깊었던 개념과 적용 후기

    이번 학습에서 가장 인상 깊었던 개념은 Spring Data JPA가 '인터페이스만으로도' 레포지토리 계층을 구현할 수 있다는 점이었다.
    처음에는 구현체도 없이 어떻게 동작하는지 이해되지 않았지만, JPA의 프록시와 스프링의 DI 원리를 공부하면서 자연스럽게 납득이 되었다.

     

    그전까지는 직접 SQL을 작성하고 쿼리 문법에 시간을 많이 쏟았었다. 쿼리 하나 바꾸려 해도 복잡한 매핑 수정이 따라붙었고, 개발보다 디버깅에 시간을 많이 쓴다는 느낌이 강했다.

     

    Spring Data JPA를 사용한 이후로는 CRUD 메서드를 손쉽게 만들 수 있었고, 필요한 쿼리도 메서드 이름만으로 자동 생성되는 부분이 정말 인상 깊었다. 기본적인 데이터 접근 코드를 줄이고, 도메인 로직에 집중할 수 있게 되면서 생산성도 자연스럽게 올라갔다.

     

    앞으로 팀 프로젝트나 개인 프로젝트를 할 때,
    단순한 데이터 접근은 최대한 Spring Data JPA로 처리하고, 복잡한 비즈니스 쿼리는 @Query나 QueryDSL로 명확하게 분리하는 방향으로 설계를 가져가려고 한다.
    또한, Repository 계층은 가능한 한 읽기/쓰기 책임을 나누는 방식으로 리팩토링해보고 싶다.

    728x90