꿈소년의 개발 이야기

[스프링 부트 개발자 온보딩 가이드 스터디] Chapter 04 JPA 기반의 To-Do 리스트 REST API 서버 개발 본문

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

[스프링 부트 개발자 온보딩 가이드 스터디] Chapter 04 JPA 기반의 To-Do 리스트 REST API 서버 개발

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

DAY 4

🔖 오늘 읽은 범위 :
🌱공부 내용: JPA 기반의 To-Do 리스트 REST API 서버 개발
👢쪽수: p.103-p.150


목표: 데이터 영속성 지원 기능 추가.

기능 요구사항: JPA, MySQL 데이터베이스를 이용하여 CRUD 기능 구현.

구현 요구사항

  • 엔드포인트: “/api/todos/v2”
  • 테스트: CRUD 기능 유닛 테스트 작성.
  • 문서화 및 테스트: Swagger 3을 사용해 스키마와 API에 대한 문서화 수행 및 테스트 가능하게 하기.

JPA 이해하기

  • 자바 객체와 RDB(관계형 데이터베이스) 간의 데이터를 효율적으로 매핑하고 관리하기 위한 표준 ORM 명세.
  • 객체 지향 언어 자바에서 클래스와 디비 테이블 간의 매핑을 쉽게 처리할 수 있도록 돕는 기술.

JPA :: Spring Data JPA

JPA

  • ORM(Object Relational Mapping) 방식
  • 왜 필요한가?
    • SQL 쿼리 관리 용이성
    • 디비 커넥션 관리 문제.
    • 복잡한 세부 사항들보다는 비즈니스 로직에 구현 집중.
  • JPA 구현체 : JPA 는 명세이자 규약, 인터페이스이다.
    • Hibernate 하이버네이트 : 실제 데이터베이스 연동하는 JPA 구현체. 대표적인 JPA 구현체이다.
    • EclipseLink
    • OpenJPA
  • JPA 대안 : JPA 구현체가 아니거나 ORM 방식이 아니지만, 디비 연동에 사용하는 기술들.
    • MyBatis : SQL 매퍼 프레임워크. SQL 쿼리를 개발자가 직접 작성하고 제어할 수 있는 구조이다.


스프링 데이터 JPA 가 포함된 스프링부트 애플리케이션 아키텍처 구조

JPA와 Hibernate

  • JPA : 자바 애플리케이션에서 ORM 구현을 위한 표준 명세.
  • Hibernate : JPA 구현체. 가장 많이 사용함. JPA 명세 구현, 배치 처리, 캐시 전략, 통계 조회 등등의 고급 기능 추가. JPA 표준 API 와 확장 기능.

JPA 애노테이션

애노테이션 설명
@Entity 붙여 놓은 클래스를 JPA 엔티티로 명시함. 데이터베이스 테이블매핑 된다.
@Id 엔티티의 기본 키(Primary Key) 지정.
@GeneratedValue 기본 키 생성 전략 지정.
strategy 속성으로 기본 키 생성 방식을 지정한다.
• GenerationType.AUTO : JPA 구현체에게 맡긴다.
• GenerationType.IDENTITY : 데이터베이스에게 맡긴다. ex) MySQL 의 AUTO_INCREMENT
• GenerationType.SEQUENCE : 시퀀스를 이용해 생성한다. 주로 Oracle 에서 사용함.
• GenerationType.TABLE : 별도의 키 생성 테이블을 통해 키를 생성한다.
@Table 엔티티와 매핑 될 데이터베이스 테이블을 지정한다.
@Column 엔티티 필드와 데이터베이스 열을 매핑한다.
추가 설정을 지정할 때 사용한다.(길이, null 허용 여부, 고유 제약 등)
@ManyToOne
@OneToMany 엔티티 사이의 연관 관계 설정.
부모 - 자식 관계를 표현.
@JoinColumn 을 통한 외래 키 지정 가능.
• @ManyToOne : 다대일 관계.
• @OneToMany : 일대다 관계.
@OneToOne
@ManyToMany 엔티티 사이의 1:1 및 다대다 관계 설정.
• @OneToOne : 두 엔티티 간의 1:1 관계.
• @ManyToMany : 다대다 관계.
@JoinTable 을 사용해 중간 테이블을 명시적으로 지정 가능함.
@JoinTable 다대다 관계(@ManyToMany) 관계에서 중간 테이블을 명시적 지정할 때 사용한다.
1:다(@OneToMany) 또는 1:1(@OneToOne) 관계에서 외래 키 맵핑을 커스텀할 때 사용한다.
두 테이블 간의 다대다 관계에서 중간 테이블을 명시적으로 지정한다. 또한 외래 키를 직접 설정할 수 있다.

스프링 데이터 JPA

  • 스프링 데이터 프로젝트의 하위 모듈. JPA 를 쉽게 사용할 수 있도록 도와준다.
  • 데이터베이스와의 상호 작용하는 코드를 최소화 하고, CRUD 작업을 훨씬 효율적으로 처리한다.
  • 쿼리를 직접 작성하지 않아도 인터페이스 메서드의 이름만으로 데이터베이스 작업을 수행할 수 있다.

스프링 데이터 JPA 주요 기능

  • Repository 기반 데이터 접근
    • CrudRepository, JpaRepository 같은 기본 인터페이스 제공 : 데이터베이스 접근 로직 구현.
    • 상속 받아서 정의한 레포지토리
    • 인터페이스는 CRUD 기본 작업을 자동 지원함.
    • 메서드 이름을 기반으로 쿼리를 자동 생성할 수도 있다.
    • 예시
      • UserRepository 인터페이스 : JpaRepository 인터페이스 상속 받았으면.
        • 메서드 구현체 자동 생성 제공한다.
        • findById(Long id) : 주어진 ID를 조건으로 사용자 정보를 조회한다.
        • save(User user) : 새 사용자를 추가하거나, 기존 사용자 정보를 수정한다.
        • deleteById(Long id) : 주어진 ID에 해당하는 사용자를 삭제한다.

JPQL 과 네이티브 쿼리 지원

  • JPQL : Java Persistance Query Language
    • SQL 과 비슷하다.
    • 엔티티 객체 기준으로 쿼리를 작성한다.
  • 더 복잡한 쿼리가 필요한 경우에 사용한다.
  • @Query 애노테이션을 사용해서 JPQL 이나 네티이브 쿼리를 직접 작성할 수 있다.

자동 Repository 생성

  • Repository 인터페이스를 자동 스캔하고 구현체를 생성한다.
  • 스프링부트 환경 하에서 별도 구성 없이 자동 구성된다. 개발자는 인터페이스만 정의 한다. 구현체를 직접 작성하지 않는다.
  • @EnableJpaRepositories 애노테이션을 사용해 스캔 범위를 조정할 수도 있다.

트랜잭션 관리 통합

  • 스프링 프레임워크의 트랜잭션 관리 기능과 긴밀하게 통합되어 있다.
  • @Transactional 애노테이션으로 트랜잭션을 적용할 수 있다.
  • Repository 메서드 실행 시, 트랜잭션이 자동으로 관리된다.
  • 스프링부트의 높은 생산성과 생산적인 개발 경험에 크게 기여하는 부분임.

DTO 패턴 이해하기

  • DTO : Data Transfer Object, 데이터 전송 객체.
    • 분산 시스템이나 원격 호출 환경에서 네트워크 오버헤드를 줄이기 위해 여러 데이터를 한 번에 전송하는 것.
    • 여러 필드를 개별 호출 하는 것이 아닌, 하나의 객체로 묶어 전달하는 방식.
    • 다양한 레이어 간 데이터 전달에 활용.
    • 스프링부트 애플리케이션에서 서비스와 컨트롤러 간 데이터 교환에 자주 사용함.
    • 외부 데이터 교환에 사용되며 구조가 더 유연하다.
    • 외부 또는 클라이언트와의 데이터 교환을 위한 모델.
  • 엔티티 Entity
    • 데이터베이스 테이블과 1:1 로 매핑되는 도메인 객체.
    • 데이터베이스와 직접 상호 작용할 때 사용한다.
    • JPA 에서 @Entity 애노테이션이 붙을 때, 해당 객체가 영속성 컨텍스트에서 관리 되도록 설정한다.
    • 일반적인 CRUD 중심 개발에서 엔티티는 순수한 데이터 표현 객체로 사용한다.
    • 비즈니스 로직은 다른 레이어에 위임한다.
    • 구조 변경 시 데이터베이스 스키마에도 영향을 미친다.
    • DB를 위한 모델.
  • 도메인 주도 설계 DDD
    • 엔티티가 자신의 데이터를 처리하는 비즈니스 규칙이나 로직을 내부에 포함하도록 설계한다.

  • DTO 와 엔티티는 명확하게 분리하여 사용하는 것이 좋다.
    • 시스템의 유연성과 유지보수성을 향상 시킨다.

DTO 사용 이점

  • 보안성과 데이터 캡슐화 유지
    • 엔티티 정보를 모두 노출하지 않고 필요한 정보만 선택해서 노출할 수 있다.
  • 레이어 간 결합도 감소.
    • DTO 는 데이터 전송 담당. 엔티티와 분리 되어 있음.
    • 비즈니스 로직을 변경하더라도 DTO 는 변경되지 않기 때문에 불필요한 코드 변경이 최소화 된다.

DTO 패턴의 단점

  • 원본 객체와 DTO 간의 변환 작업이 필요하다.
    • ModelMapper or MapStruct
    • 변환 로직이 많아지면 유지 보수에 부담을 주기도 한다.
  • 변환 작업은 추가적인 오버헤드 수반하며 성능 저하를 유발할 수도 있다.
  • 대규모 트랜잭션 시스템에서는 성능 최적화가 중요한 경우, DTO 사용에 따르는 트레이드 오프를 적절히 고려해야 한다.

프로젝트 초기화

  • 도커를 이용하여 MySQL을 WSL2 환경에 설치한다.

도커를 이용한 MySQL 설치 및 설정

  • 도커를 이용해 MySQL 8.0 설치
  • 도커 사용하는 이유
    • 다른 애플리케이션과 격리된 환경에서 실행하여 종속성 충돌을 방지한다.
    • 간단한 명령어로 설치 및 배포 가능하다.
    • 도커 컨테이너 내에서 의존성 문제를 해결하여 로컬 환경에서 충돌을 방지한다.
    • 이로 인해 시스템을 깨끗하게 유지할 수 있다.

MySQL 도커 컨테이너 실행

  • WSL2 터미널 열기.

  • MySQL 도커 컨테이너 실행.

  • 실행 명령어

      docker run --name mysql-todo \
          -e MYSQL_ROOT_PASSWORD=dev_password \
          -e MYSQL_DATABASE=todo_db \
          -e MYSQL_USER=todo_user \
          -e MYSQL_PASSWORD=dev_password \
          -p 3306:3306 \
          -v mysql-todo-data:/var/lib/mysql \
          -d mysql:8.0
    • 명령어로 인해 실행되는 작업들
      • MySQL 8.0 컨테이너 실행.
      • todo_db 데이터베이스 생성.
      • todo_user 사용자 계정 생성.
      • 루트 비밀번호 및 사용자 비밀번호 설정.
      • 3306 포트 매핑
        • 호스트 3306 포트와 컨테이너 3306 포트 연결. 호스트에서 접근할 수 있도록 함.
      • 데이터 영구 저장
        • mysql-todo-data 라는 docker 볼륨을 /var/lib/mysql 디렉토리에 매핑하여 MySQL 데이터가 영구적으로 저장된다.

MySQL 컨테이너 상태 확인

  • MySQL 컨테이너 정상 동작인지 확인하는 명령어.

      docker ps --filter "name=mysql"
    • 결과 화면에서 해당 컨테이너 목록이 나타나고, STATUS ‘Up’ 으로 표시되면 정상 실행이다.
  • 도커 데스크탑 앱에서 컨테이너 정상 동작 확인.

    • 컨테이너스 항목 선택 → 목록에서 Status ‘Running’ 이 표시되면 정상 실행 중.

MySQL 접속 확인

  • 접속 명령어.

      docker exec -it mysql-todo mysql -u todo_user -p
    • 명령어 입력 후 패스워드 입력 창 나타남.
    • 위에서 나온 패스워드 입력. ex) dev_password
    • 개발 환경에서만 사용하는 암호이므로 운영 환경에서는 더 강력한 암호를 사용해야 함.
    • MySQL 셀 접속 완료.
  • MySQL 셀 접속 후 데이터베이스 생성 확인

      SHOW_DATABASES;

테이블 스키마

  • 예제 todo 테이블 스키마
| 컬럼 | 데이터 타입(크기) | 제약 | 설명 |
| --- | --- | --- | --- |
| id | BIGINT | AUTO_INCREMENT PRIMARY KEY | 기본 키 |
| title | VARCHAR(255) | NOT NULL | 제목 |
| description | TEXT |  | 설명 |
| completed | BOOLEAN | NOT NULL DEFAULT FALSE | 완료 여부 |
| created_at | TIMESTAMP | NOT NULL DEFAULT CURRENT_TIMESTAMP | 레코드 생성 시간 |
  • 이 테이블은 미리 생성할 필요 없음.

    • application.properties 에 적용한 옵션으로 엔티티 클래스에 정의된 대로 테이블 자동 생성 및 스키마 업데이트(필요시)

        # Hibernate 가 디비 스키마를 자동 업데이트 하도록 설정.
        spring.jpa.hibernate.ddl-auto=update
  • DDL 예시

      CREATE TABLE todo_db.todo {
              id BIGINT AUTO_INCREMENT PRIMARY KEY, -- Todo ID, 자동 증가 기본키
              title VARCHAR(255) NOT NULL, -- Todo 제목, 널이 아님.
              description TEXT, -- Todo 설명
              completed BOOLEAN NOT NULL DEFAULT FALSE, -- 완료 여부, 기본값은 false.
              created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -- Todo 생성 시간
      }

settings.gradle - 프로젝트 명 변경

  • rootProject.name = ‘변경할 프로젝트 이름’

build.gradle - JPA, MySQL JDBC 드라이버 의존성 추가

  • JPA, MySQL JDBC 커넥터, TestContainers 의존성 추가.

      // Spring Data JPA 의존성 추가
      implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
      // MySQL JDBC 드라이버 의존성 추가.
      implementation 'mysql:mysql-connector-java:8.0.32'
      // Import TestContainers for JUnit 5
      // 임시 MySQL 컨테이너 생성 및 유닛 테스트 수행을 위한 의존성 추가.
      testImplementation 'org.testcontainers:junit-jupiter:1.20.1'
      testImplementation 'org.testcontainers:testcontainers:1.20.1'
      testImplementation 'org.testcontainers:mysql:1.20.1'

application.properties 수정

  • 스프링부트 애플리케이션 설정과 동작 방식을 정의하고 관리하는 중요 구성 파일.

  • API 서버가 MySQL에 접근할 수 있도록 여기에 설정을 추가해야 한다.

      # 로깅 및 관리 도구에서 식별하기 위한 애플리케이션 이름 설정.
      spring.application.name=todo-mysql
    
      # 디비 URL 설정.
      spring.datasource.url=jdbc:mysql://localhost:3306/todo_db
      # 디비 사용자 이름 설정.
      spring.datasource.username=todo_user
      # 디비 사용자 비밀번호 설정.
      spring.datasource.password=dev_password
      # 디비 접근 드라이버 설정.
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
      # Hibernate 가 디비 스키마를 자동 업데이트 하도록 설정.
      spring.jpa.hibernate.ddl-auto=update
      # SQL 쿼리 출력 설정.
      spring.jpa.show-sql=true

JPA 기반 To-Do 리스트 API 서버 구현

  • 인 메모리 기반 API 서버 구현: 데이터 영속성 보장되지 않음 ⇒ 서버 종료 또는 재시작 할 경우 데이터가 유실 됨.

엔티티, DTO, 매퍼 작성

  • 엔티티: 데이터베이스에 작업을 저장하고 읽을 때 사용.
  • DTO: 비즈니스 로직과 API 클라이언트와의 상호 작용할 때 사용.

엔티티 클래스

  • JPA 제공 애노테이션
| 애노테이션 | 설명 |
| --- | --- |
| @Entity | JPA 엔티티 클래스로 정의한다. |
| @Id | id 필드를 엔티티의 기본 키로 지정한다.
지정된 id 필드는 데이터베이스 테이블의 기본 키 컬럼과 매핑된다.
@Column 애노테이션 역할도 겸한다. |
| @GeneratedValue | id 필드가 자동 생성되는 값임을 나타난다.
strategy=GenerationType.IDENTITY : 기본 키 생성을 데이터베이스에 맡기는 전략. 주로 MySQL 같은 디비에서 AUTO_INCREMENT 속성으로 기본 키 생성하여 사용함. |
| @Column | 엔티티 클래스 필드를 데이터베이스 테이블의 열과 매핑 처리.
이름이 같은 경우에는 생략도 가능함. 명시적으로 하기 위해서 보통 붙임. |

DTO 클래스

  • RequestDto: 클라이언트 요청을 표현하는 DTO.
  • ResponseDto: 서버 응답을 표현하는 DTO.

Mapper 클래스

  • 엔티티와 DTO 로 분리했기 때문에 중간에 변환을 담당할 클래스가 필요하다.
  • 매퍼 클래스는 이 역할을 수행한다.

리포지토리 레이어 - Repository → JpaRepository 인터페이스 확장하기

  • JpaRepository 인터페이스: 스프링 데이터 JPA 에서 제공하는 인터페이스. 기본 CRUD 기능을 자동으로 제공한다.
  • 두가지 타입의 매개 변수: 엔티티와 엔티티의 기본 키 타입.
  • 스프링 데이터 JPA 가 자동으로 구현체를 생성해준다.

서비스 레이어 - 데이터베이스 연동에 대한 데이터 무결성 보장 처리

  • @Transactional(readOnly = true): 읽기 전용 트랜잭션. 데이터 조회 메서드에 적용하는 편. 성능 최적화 및 데이터베이스 쓰기 잠금 방지. 읽기 작업 빠르게 수행.
  • @Transactional: 기본 트랜잭션. 데이터베이스 추가, 수정, 삭제 작업 메서드에 적용하는 편. 변경 내용이 모두 적용 되거나 모두 취소 하도록 함. 저장 중 오류 발생 시 롤백 진행함.

REST 컨트롤러 - 의존성 주입

  • 필드 의존성 주입은 추천하지 않음. 테스트가 어렵고 불변성 보장이 어렵다. 객체 순환 의존성을 발견하기 어렵다.

  • 되도록 생성자 의존성 주입을 추천한다.

      @Autowired private TodoService todoService; // 필드 의존성 주입. 추천하지 않음.
    
      //------------------------
    
      // 생성자 의존성 주입.
      @Autowired
      public TodoService(TodoRepository todoRepository) {
          this.todoRepository = todoRepository;
      }

Testcontainers 사용한 서비스 테스트

@SpringBootTest
@Testcontainers
@ExtendWith(SpringExtension.class)
public class TodoServiceTests {

    @Container
    public static MySQLContainer<?> mysqlContainer =
            new MySQLContainer<>("mysql:8.0.32")
                    .withDatabaseName("testdb")
                    .withUsername("test")
                    .withPassword("test");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mysqlContainer::getJdbcUrl);
        registry.add("spring.datasource.username", mysqlContainer::getUsername);
        registry.add("spring.datasource.password", mysqlContainer::getPassword);
    }

    @Autowired private TodoService todoService;
    @Autowired private TodoRepository todoRepository;

    private Long todo1Id;
    private Long todo2Id;

    @BeforeEach
    void setUp() {
        todoRepository.deleteAll();
        todoService = new TodoService(todoRepository);
        todo1Id = todoService.save(new TodoRequestDto("Test Todo 1", "Description 1")).getId();
        todo2Id = todoService.save(new TodoRequestDto("Test Todo 2", "Description 2")).getId();
    }

    @Test
    void testFindAll() throws Exception {
        List<TodoResponseDto> todos = todoService.findAll();
        assertThat(todos).hasSize(2);
    }

    @Test
    void testSaveTodo() throws Exception {
        TodoRequestDto todo = new TodoRequestDto("New Todo", "New Description");
        todoService.save(todo);
        assertThat(todoService.findAll()).hasSize(3);
    }

    @Test
    void testFindById() throws Exception {
        TodoResponseDto todo = todoService.findById(todo1Id);
        assertThat(todo).isNotNull();
        assertThat(todo.getTitle()).isEqualTo("Test Todo 1");
    }

    @Test
    void testUpdateTodo() throws Exception {
        TodoRequestDto updatedTodo =
                new TodoRequestDto("Updated Todo", "Updated Description", true);
        todoService.update(todo1Id, updatedTodo);
        TodoResponseDto todo = todoService.findById(todo1Id);
        assertThat(todo.getTitle()).isEqualTo("Updated Todo");
        assertThat(todo.getDescription()).isEqualTo("Updated Description");
        assertThat(todo.isCompleted()).isTrue();
    }

    @Test
    void testDeleteTodo() throws Exception {
        todoService.delete(todo1Id);
        assertThat(todoService.findAll()).hasSize(1);
        assertThat(todoService.findById(todo1Id)).isNull();
    }
}

@Testcontainers

  • 클래스 단위에서 Testcontainers 사용을 활성화하는 애노테이션.

  • 해당 클래스 내에 선언된 @Container 필드를 인식하고 각 테스트에 대해 컨테이너 생명 주기를 관리한다.

  • @Testcontainers 가 없을 경우, Testcontainers는 컨테이너 필드를 자동으로 관리하지 않고, 개발자가 수동으로 컨테이너 시작과 종료를 처리해야 한다.

    Testcontainers

  • 테스트 환경을 컨테이너화해서 처리한다. 실제 테스트 실행 시, 도커 인스턴스가 생성되고 실행된 후 테스트가 종료되면 삭제된다.

@ExtendWith(SpringExtension.class)

  • JUnit 5 확장 기능 연결할 때 사용한다.
  • 스프링 테스트 컨텍스트를 JUnit 5 테스트와 통합한다.
  • 테스트 실행 시 스프링 애플리케이션 컨텍스트가 자동으로 생성 → 테스트 클래스에 @Autowired 로 의존성 주입할 수 있다.
  • 트랜잭션 관리, 설정 빈 로딩 등 스프링이 제공하는 다양한 기능을 테스트 코드에 제공한다.
  • 사용하지 않을 경우, 단순한 JUnit 테스트로만 동작한다. 스프링 컨텍스트가 없으므로 모두 수동으로 설정해야 한다.

@Container

  • Testcontainers 에서 제공하는 애노테이션.
  • 테스트 중에 사용할 컨테이너를 선언함.
  • 데이터베이스 또는 컨테이너화 된 서비스를 테스트 환경에서 실행할 수 있다.
  • 선언된 컨테이너는 테스트 실행 전에 자동으로 시작되고, 테스트가 끝나면 종료된다.

동적 속성 주입 DynamicPropertySource

  • @DynamicPropertySource: 테스트 실행 중 필요한 속성(properties)을 동적으로 주입한다.

  • @Container 로 지정한 컨테이너가 실행 될 때, 생성된 데이터베이스 URL, 사용자 이름과 비밀번호 등등 데이터베이스 연결에 필요한 정보를 스프링 애플리케이션 컨텍스트에 자동 주입한다.

  • 컨테이너에서 생성된 데이터베이스 연결 정보가 동적으로 주입되어 JUnit5 테스트에서 해당 데이터베이스를 사용할 수 있다.

  • 동적 속성 주입을 사용하지 않을 경우 아래와 같은 방법으로 해야 한다.

    • application-test.properties 파일에 데이터베이스 정보를 명시적으로 설정해야 한다.

        # 디비 URL 설정.
        spring.datasource.url=jdbc:mysql://localhost:3306/test_db
        # 디비 사용자 이름 설정.
        spring.datasource.username=test
        # 디비 사용자 비밀번호 설정.
        spring.datasource.password=test

Test 실행

  • gradle test
  • PASSED 로 나타나면 정상.

실행 및 Swagger-UI 를 이용한 API 테스트

스프링 부트의 데이터베이스 연동

  • JPA - 자바 진영에서 공식 채택된 표준 ORM 인터페이스. 스프링 부트는 JPA 연동을 가장 강력하게 지원. 실무에서 가장 많이 사용함.
  • JPA 의 가장 큰 장점
    • 생산성과 유지보수성.
    • 기본 CRUD 작업 및 쿼리는 자동으로 처리 해줌.
    • 스프링 부트와 결합이 잘 되어 있어 트랜잭션 관리, 캐싱, 데이터 검증 등 부가 기능까지 한 번에 활용할 수 있다.
  • 단점
    • 복잡한 쿼리나 대용량 데이터 처리가 필요할 경우, JPA 동작 원리를 깊이 이해하지 못한 경우에는 오히려 성능 이슈나 숨은 버그에 시달릴 수 있음.
    • JPA 영속성 컨텍스트, 지연 로딩, 플러싱, 더티 체킹 등 내부 메커니즘이 복잡하기 때문에 예상치 못한 결과가 나올 수 있음.
  • 데이터베이스 고유 기능이 중요한 프로젝트에서는 활용하는 데에 한계가 있음. 인덱스 튜닝이나 저장 프로시저 활용.
  • 대안
    • MyBatis 같은 프레임워크와 혼용해서 사용하는 경우가 많음.
    • 아주 복잡한 쿼리나 성능이 중요한 영역에서는 MyBatis, QueryDSL, 또는 SQL 직접 작성 방식으로 보완한다.
  • 으아 에디터를 인텔리제이 → 비주얼스튜디오 코드로 변경했더니 책 그대로 안 되네 ㅡ.ㅡ;;;;
  • 다시 인텔리제이 아이디어로 변경. 그리고 구독했음!!
  • https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format

  • https://github.com/google/google-java-format

  • https://marketplace.visualstudio.com/items?itemName=JoseVSeb.google-java-format-for-vs-code ⇒ 이걸 설치하면 구글 자바 포맷이랑 맞춰짐.

    • 추가 설정 : 설정 → 확장 → Google Java Format for VS Code → Java.Format.Settings.Google.Extra

            "[java]": {
                "editor.defaultFormatter": "josevseb.google-java-format-for-vs-code"
            },
            "java.format.settings.google.extra": "--aosp"
  • 자바 포맷팅 옵션 일부 변경 → vs code 확장으로 설치한 것과 맞추기 위함.

      // Spotless 플러그인을 사용하여 코드 양식을 자동으로 맞춰줍니다.
      spotless {
          // Java 파일에 대한 포맷팅 설정을 추가합니다.
          java {
              // Google Java Format 적용.
              // reflowLongStrings => + 연산자를 이용하여 읽기 좋게 변경함.
              googleJavaFormat('1.34.1').aosp().reflowLongStrings().reorderImports(true)
          }
      }
    
      // build 할 때 spotlessApply 를 실행하며 자동으로 코드의 양식을 맞춥니다.
      build.dependsOn 'spotlessApply'
  • TestContainer 테스트 시 도커 연결 실패 현상 수정 건

    • 오류 메시지

        java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configuration
      • TestContainers 와 Docker 사이의 API 버전 불일치로 인함.
    • 해결

        // src/main/resources/docker-java.properties -> 없다면 생성.
        api.version=1.44
  • 메서드 이름 기반 쿼리 자동 생성


🤟소감 3줄 요약

  • JPA 는 스펙이므로 따로 찾아봐야 하는 게 맞았다. 따로 공부할 정도로 양이 많다.
  • 테스트 코드 구현 기술을 익히면 나중에 좋다.
  • 스프링 부트는 자료가 있으나, 이를 취합하여 정리한 게 많이 없는 것 같다. 아쉽다.
반응형