꿈소년의 개발 이야기

[스프링 부트 개발자 온보딩 가이드 스터디] Chapter 05 고급 JPA 기반의 마이크로블로그 REST API 서버 개발 본문

Do it 스터디!/스프링 부트 개발자 온보딩 가이드 스터디!

[스프링 부트 개발자 온보딩 가이드 스터디] Chapter 05 고급 JPA 기반의 마이크로블로그 REST API 서버 개발

fogthegreat 2026. 4. 3. 20:19
반응형

DAY 5

🔖 오늘 읽은 범위 :
🌱공부 내용: Chapter 05 고급 JPA 기반의 마이크로블로그 REST API 서버 개발
👢쪽수: p.151-p.218


  • 목표: REST API 제공하는 마이크로블로그 스프링부트 앱 구현.
  • 기능 요구 사항
    • 사용자 계정 CRUD 수행.
    • 게시글 데이터 CRUD 수행.
    • 다른 사용자에 대한 팔로우 정보 CRUD 수행.
    • 감사 기능을 통한 데이터 변경 이력 추적 수행.
    • 인증 기능 미제공.
  • 구현 요구 사항
    • 엔드포인트: 모든 API 엔드포인트는 ‘ /api/todos/v1’ 으로 시작한다.
    • 테스트: CRUD 기능 유닛 테스트.
    • 문서화 및 테스트: 스웨거3 사용한 API 문서화 및 테스트.

프로젝트 초기화

프로젝트 스캐폴드 생성 및 다운로드

스프링 이니셜라이저

옵션 설명
dependencies web REST API 서버 개발 핵심 의존성 ‘org.springframework.boot’ 추가.
서블릿 컨테이너 톰캣 내장. 스프링 MVC 기반 앱, REST API 서버 개발에 필요한 클래스 라이브러리 제공.
javaVersion 21 프로젝트 자바 버전 설정.
type gradle-project 빌드 도구로 gradle 설정.
bootVersion 3.3.1 사용할 Spring Boot 버전 설정.
groupId com.asdf 프로젝트 그룹 아이디 설정.
name minilog 프로젝트 이름 설정.
artifactId minilog 프로젝트 아티팩트 아이디 설정. 프로젝트 고유 식별자. 생성된 jar 파일 이름으로 사용 됨.
packageName com.asdf.minilog 기본 패키지 이름 설정. 코드 네임스페이스 정의.
curl https://start.spring.io/starter.zip \
        -d dependencies=web \
        -d type=gradle-project \
        -d bootVersion=3.3.1 \
        -d groupId=com.asdf \
        -d name=minilog \
        -d artifactId=minilog \
        -d packageName=com.asdf.minilog \
        -o minilog-jpa.zip

도커 MySQL 설치 및 설정

minilog용 MySQL 도커 컨테이너 실행

docker run --name mysql-minilog \
        -e MYSQL_ROOT_PASSWORD=dev_password \
        -e MYSQL_DATABASE=minilog_db \
        -e MYSQL_USER=minilog_user \
        -e MYSQL_PASSWORD=dev_password \
        -p 3307:3306 \
        -v mysql-minilog-data:/var/lib/mysql \
        -d mysql:8.0
  • 호스트 3307 포트를 이용해서 도커 컨테이너의 3306 포트에 접속할 수 있도록 한다.

Minilog API 서버 구현

  • 고수준 JPA.
  • 스프링 전역 예외 처리.
  • 감사 기능을 이용한 데이터 변경 추적 방법.
  • JPA 활용.
  • 관계 매핑 애노테이션 이해.

스프링 전역 에러 처리기 작성

  • Global Exception Handler
  • 스프링 부트 애플리케이션에서 발생하는 예외를 한 곳에서 일관되게 처리하기 위한 장치.
  • 예외 처리 상황을 중앙에서 제어하고 처리할 수 있다.
  • @ControllerAdvice
    • 스프링 프레임워크에서 제공하는 애노테이션.
    • 전역적인 예외 처리, 데이터 바인딩, 모델 객체에 대한 조언(Advice)을 설정할 수 있다.
    • 조언 ⇒ 스프링 AOP 의 조언을 의미함.
    • AOP 핵심: 핵심 로직에 개입하지 않고도 실행 과정 전후나 특정 시점에 부가적인 동작을 삽입할 수 있다는 점.
    • 컨트롤러 메서드 실행 전후, 예외 발생 시점에 개입해 공통 기능을 제공한다.
    • 할 수 있는 일들.
      • 일관된 예외 처리 로직 수행.
      • 데이터 바인딩 규칙 설정.
      • 모든 컨트롤러에 공통 모델 데이터 추가.
    • 애플리케이션 전반에 걸쳐 여러 컨트롤러에서 반복하는 예외 발생을 한 곳에서 처리 가능하다.
    • @ExceptionHandler 와 조합해서 함께 사용한다.
    • 모든 컨트롤러에 기본 적용되나, 속성을 지정해서 특정 패키지나 클래스에만 적용할 수 있다.
    • 개별 컨트롤러에 @ExceptionHandler 가 정의되어 있다면, 해당 핸들러가 우선 적용된다.

엔티티, DTO, 매퍼 구현

  • 엔티티 관계 정의.
    • OneToMany
    • ManyToOne
  • 엔티티 역할 및 속성 정의.
    • 생성 시간 createdAt, 수정 시간 updatedAt ⇒ 감사 목적을 위한 추적.

엔티티 작성

  • 감사 기능
    • 데이터가 언제 생성되고, 마지막으로 수정되었는지 추적한다.
    • @CreatedDate
      • 레코드 처음 생성 시간을 자동 기록한다.
    • @LastModifiedDate
      • 레코드 마지막 수정 시간을 기록한다.
    • @EntityListeners(AuditingEntityListener.class) 를 통해 추가된다.
  • 필드 관계 설정
    • @OneToMany(mappedBy = “author”, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
      • mappedBy = “author”
        • 1:N, 일대다 관계를 맺을 때, mappedBy 로 지정한 필드를 통해서 형성한다.
      • cascade = CascadeType.ALL
        • 엔티티의 변경 사항이 관계 된 엔티티에도 전파되도록 설정한다.
        • 단일 부모 엔티티에서 특정 엔티티를 일대다의 다수 엔티티들의 컬렉션에서 제거해도 자동으로 삭제 되지 않는다.
        • 자동 삭제하려면 orphanRemoval 값을 설정해줘야 한다.
      • orphanRemoval = true
        • 부모 엔티티의 다수 관계의 엔티티 컬렉션에서 특정 엔티티를 제거할 때, 해당 엔티티가 데이터베이스에서도 자동 삭제 된다.
        • 만약, 부모 엔티티가 삭제될 때, 다수 쪽 엔티티 컬렉션이 모두 삭제 되려면 CascadeType.REMOVE 를 명시해야 한다.
      • fetch = FetchType.LAZY
        • 부모 엔티티 조회할 때, 연관된 다수 쪽 엔티티 컬렉션이 즉시 로딩되지 않고, 실제로 접근할 때 JPA 가 SELECT 쿼리를 추가로 실행해서 데이터를 가져온다.
        • FetchType.EAGER 를 설정하면 JPA 구현체 hibernate 는 JOIN 또는 별도의 SELECT 쿼리를 사용해 다수쪽 엔티티 컬렉션을 즉시 로딩하도록 한다.
    • @ManyToOne(fetch = FetchType.EAGER)
      • N:1, 다대일 관계를 만든다.
      • FetchType.EAGER: 조회 시 유저 정보도 즉시 로딩된다.
    • @JoinColumn(name = “author_id”, nullable = false)
      • @JoinColumn: 해당 엔티티에서 다른 엔티티의 ID를 참조하는 외래 키(FK)를 정의하는 데 사용한다.
        • 해당 엔티티 테이블의 author_id 컬럼이 다른 엔티티 테이블의 ID 값을 참조할 수 있도록 설정된다.
      • nullable = false: 이 설정으로 통해 무결성을 보장한다.
    • @Index
      • 특정 필드에 인덱스를 설정한다.
      • 조회 성능을 향상 시킨다.
    • @UniqueConstraint(columnNames = {”follower_id”, “followee_id”})
      • follower_id 와 followee_id 의 조합이 고유해야 함을 보장하는 제약 조건을 만든다.
      • 이 제약 조건으로 중복 저장되지 않도록 하여 데이터 일관성을 제공한다.

DTO 작성

  • DTO 객체: 컨트롤러와 서비스 계층 간 데이터 교환을 위해 사용한다.
  • 엔티티 직접 노출이 되지 않아, 데이터 보호에 유리하며, 전송해야 할 데이터만 선별해서 불필요한 데이터 전송을 최소화할 수 있다.

리포지토리 레이어 구현

  • 스프링 데이터 JPA 핵심 기능 3가지
    • JpaRepository 인터페이스 상속하여 구현체를 자동 생성하는 기능.
      • save(), delete(), findById() 같은 JpaRepository 기본 메서드 제공한다.
      • 메서드 이름 기반으로 쿼리 유추하여 실행하는 파생 쿼리 메서드 제공한다.
    • 파생 쿼리 Derived Query
      • Query Method; 파생 쿼리 메서드.
      • 메서드 이름의 네이밍 규칙을 분석해 내부적으로 쿼리를 자동 생성한다.
      • JpaRepository에서 제공하지 않는 조회 기능이 필요한 경우 유용하다.
      • 예시
        • findById(): 별도 구현없이 바로 사용할 수 있는 기본 Jpa 메서드로 제공된다.
        • findByUsername(): username 필드 값이 메서드에 전달된 인자와 같은 레코드를 탐색하는 기능을 수행함. 선언만 해두면 런타임에 자동으로 생성된다. By 는 검색 조건의 시작을 의미한다.
        • findByFollowerIdAndFolloweeId(): And 를 통해 두 필드 followerId, followeeId 를 조건으로 지정하여 주어진 값과 일치하는지 확인하는 쿼리가 자동 생성된다.
    • JPQL
      • Java Persistence Query Language
      • 더 복잡한 조회나 조건이 필요한 경우 사용할 수 있다.
      • JPA 엔티티와 속성을 대상으로 동작하는 객체 지향 쿼리 언어이다.
      • 리포지토리 메서드에 @Query 애노테이션을 붙여 직접 작성한다.
      • JPA 구현체(Hibernate)는 작성된 JPQL → SQL 로 변환해서 처리한다.
      • 단순 파생 쿼리로 표현하기 어려운 복잡한 조인, 정렬, 조건 기반 조회 등을 구현할 때 사용한다.

서비스 레이어 구현

  • 클래스에 @Transactional 애노테이션을 적용하는 경우.
    • 클래스 내 모든 메서드에 트랜잭션을 적용한다.
    • 데이터 변경 작업이 안전하게 하도록 보장한다.
    • 예외 발생 시 자동 롤백을 수행한다.
    • 특정 메서드는 읽기 연산을 위한 성능 최적화와 데이터 무결성 보장하기 위해 별도로 @Transactional(readOnly = true) 로 설정하기도 한다.
      • 쓰기 잠금을 생략하여 리소스를 절약한다.
      • 조회 성능을 높일 수 있다.
      • 트랜잭션이 종료될 때 변경 내용을 커밋하지 않도록 처리해서, 예상 못한 데이터 변경을 방지 할 수 있다.
  • 클래스에 @Transactional(isolation = Isolation.REPEATABLE_READ) 애노테이션을 적용하는 경우.
    • 데이터베이스 트랜잭션의 일관성과 무결성을 보장하기 위해 트랜잭션 격리 수준을 지정한다.
    • Isolation.REPEATABLE_READ: 트랜잭션 내에서 같은 데이터를 반복 조회할 때, 다른 트랜잭션에 의해 수정된 내용이 보이지 않도록 보호하는 격리 수준이다. 균형 잡힌 격리 수준으로 본다.
    • 데이터 조회 시 일관성 유지한다.
    • 트랜잭션 진행 중 다른 곳의 데이터 변경으로 인한 불일치 문제를 방지 할 수 있다.
    • 격리 수준이 높아질수록 잠금(Lock)이 많이 발생해서 성능에 악영향을 줄 수도 있다.
    • 성능과 데이터 일관성 사이의 균형을 고려하여 선택해야 한다.
반응형