제미로그 | JPA를 활용한 API 개발에서 배운 내용 정리하기

2025. 8. 1. 07:04Projects/JemiLog

[API 개발] 

1. API URL 및 HTTP 메서드 설계

  • RESTful API는 리소스 중심(명사) + 행위는 HTTP 메서드로 구분 
    • 목록 조회: GET + /api/tags
    • 단건 조회: GET + /api/tags/{id}
    • 등록: POST + /api/tags
    • 수정: PUT + /api/tags/{id}
    • 삭제: DELETE + /api/tags/{id}
  • URL의 식별자(id)는 @PathVariable, 요청 본문은 @RequestBody로 받는다.
  • 상태코드는 ResponseEntity를 통해 명확히 반환한다.

2. Controller 메서드 시그니처 구성 규칙

@PutMapping("/{tagId}")
public ApiResponse<Void> updateTag(
    @PathVariable Long tagId,
    @RequestBody TagUpdateRequest request
)
  • URL 경로 ID → @PathVariable
  • 등록/수정 데이터 → @RequestBody
  • 쿼리 파라미터 → @RequestParam으로 받고 null 체크로 분기

3. API 응답 구조 통일: ApiResponse<T>

public class ApiResponse<T> {
    private boolean success;
    private T data;
    private String message;

    public static <T> ApiResponse<T> ok(T data) {...}
    public static ApiResponse<Void> fail(String message) {...}
}
  • 일관된 포맷으로 응답 → 클라이언트 처리와 디버깅 편의성 ↑
  • 단순 메시지는 SimpleMessageResponse 같은 별도 DTO로 처리 가능

4. DTO 설계 및 구성 방식

  • 실무에서는 도메인 객체(엔티티)와 API 요청/응답을 분리하기 위해 DTO를 사용한다. 
    • Entity를 직접 노출하면 보안/유지보수 측면에서 위험함 (불필요한 정보 노출, 구조 변경 시 영향 등).
    • 요청/응답 요구사항은 Entity와 다를 수 있기 때문에 별도의 구조가 필요함.
    • controller DTO 도메인 하위 패키지에 함께 두고, dto/ 폴더를 만들어 구분하면 관리와 확장성이 좋다
  • DTO 네이밍 
    • 요청용 DTO CreateXXXRequest, UpdateXXXRequest 등으로, 응답용 DTO XXXResponse 이름을 구분한다.
    • Dto 접미사는 명확한 의미 전달이 필요한 경우에만 사용하며, 생략하는 것도 가능하다.
목적 DTO 네이밍  설명 
생성용 TagCreateRequest 생성에 필요한 필드만 담기
수정용 TagUpdateRequest 수정 가능한 필드만 담기
조회용 TagResponse Entity의 값을 응답용으로 가공
상세 조회용 TagDetailResponse 필요한 필드만 뽑아서 클라이언트에 보여줄 용도
목록용 TagListResponse + TagSummaryDto 여러 개일 때 응답 구조화
  • DTO 변환 메서드 네이밍 규칙
변환 상황 메서드명 예시
DTO → Entity 변환 of()  Tag.of(TagDTO); 
Entity → DTO 변환 from()  TagDTO.from(Tag); 

5. Service/Controller/Repository 네이밍 컨벤션

  • Service/Controller 메서드: 동사 + 도메인명
    예: createTag(), getTags(), updateTheme()
  • Repository 메서드: Spring Data JPA 규칙
    예: findById(), deleteByName()

6. Service 계층의 역할과 구조

  • 복잡한 비즈니스 로직, 연관관계 처리, DTO 변환 등을 책임짐
  • 단순 CRUD도 DTO 변환 책임은 Service에 둔다
  • 여러 Service 간 의존이 있어도, 비즈니스 상 의미가 있다면 괜찮다. 단, 역할과 책임은 명확히 분리할 것.
  • 트랜잭션은 읽기 전용(@Transactional(readOnly = true))과
    쓰기(@Transactional) 로직에 따라 구분해서 사용하는 것이 효율적이다.

7. 컬렉션 포함 응답 구조

  • 예: ThemeResponse에 포함된 List<TagResponse>
  • 용도에 따라 포함 여부 결정 (ex. 미리보기 vs 상세 조회)
  • 목록 응답은 페이징을 고려해 ThemeListResponse 형태로 감싸는 것이 좋음

8. JPA 관련 API 설계에 영향 주는 부분

1) Builder와 생성자 사용 시 주의

  • Builder 사용 시 컬렉션 초기화가 안 되므로 주의
  • 생성자에서 직접 초기화하거나, 필드 선언 시 new ArrayList<>()로 초기화 필요

2) 연관관계 편의 메서드 분리

  • 중간 엔티티 사용 시, 연관관계 로직은 도메인 내부가 아닌 별도 서비스에 위임
  • 예: TagThemeService에서 Theme–Tag 연관 처리

[JPA 개발]

1. 엔티티 설계와 객체지향적 구조

  • 엔티티 생성은 생성자와 update() 메서드로 역할을 분리한다.
    → 생성자는 초기값 설정, update는 이후 상태 변경 담당
  • setter 사용은 지양하고, 의미 있는 메서드명으로 필드 변경을 캡슐화한다.
  • @NoArgsConstructor(access = PROTECTED)를 붙여 JPA 프록시 생성을 허용한다.
  • BaseEntity는 @MappedSuperclass로 설계하고, @PrePersist, @PreUpdate를 통해 생성일/수정일 자동 관리 가능.
  • boolean 필드의 경우 isDeleted()가 자동 생성되지 않으므로, 필드명을 deleted로 하거나 직접 구현한다.

2. 연관관계 매핑과 관리 전략

  • 다대다 관계는 반드시 중간 엔티티를 만들어서 처리한다. (예: TagTheme)
  • 중간 엔티티도 JPA의 엔티티로 정의해야 하며, 연관관계 편의 메서드를 통해 연계 상태를 명확히 관리한다.
  • 연관관계 설정은 중간 엔티티 내부에서 직접하지 않고, 외부에서 static factory method로 생성과 연결을 함께 처리한다.
  • 양방향 연관관계는 꼭 필요한 경우에만 설정하고, 양쪽 컬렉션을 모두 관리해야 객체 상태가 일관된다.
  • 연관관계를 정리할 때는 삭제 대상을 복사한 후 for-each로 remove, delete 처리한다.
  • 단방향 연관관계는 Missionlog, Recommend처럼 단순 참조 관계에서 사용하며, 조회 시 repository를 직접 활용하는 것이 실용적이다.

3. 영속성 관리와 저장 전략

  • cascade = CascadeType.ALL을 설정하면 연관된 엔티티도 함께 persist()됨.
  • 단, 연관 객체가 영속 상태가 아니면, 먼저 persist()해야 오류가 발생하지 않음.
  • flush()와 clear()를 활용해 영속성 컨텍스트를 초기화하고, DB에 반영된 데이터를 테스트로 검증할 수 있음.
  • 더티 체킹(dirty checking)은 영속 상태의 엔티티에서 필드 값이 바뀌면 트랜잭션 커밋 시 자동으로 update 쿼리가 나감.
  • @Column(updatable = false)는 해당 필드를 더티 체킹 대상에서 제외시킴.
    → 주의: 이후 수정할 가능성이 있는 필드(ex. isDeleted)에는 사용하지 말아야 한다.

4. soft delete 구현 전략

  • 실제 삭제 대신 is_deleted(lombok 사용시 deleted) 같은 boolean 필드를 사용해 논리 삭제 구현
  • 필터링 유지 방법
    • Hibernate 6.3 이상에서는 @SQLRestriction 사용
    • 단, @MappedSuperclass에는 적용되지 않음
    • 또는 모든 쿼리에 수동으로 조건 명시
  • @SQLDelete를 함께 사용하면 DELETE 호출 시 UPDATE is_deleted = true가 자동 실행됨
  • soft delete는 cascade에 영향을 주지 않음 → 연관 객체도 직접 삭제하거나 상태를 조정해야 함
  • 중간 엔티티는 soft delete보다 hard delete가 성능/관리 면에서 유리한 경우가 많음

5. JPA스럽게 사고하기

  • JPA는 엔티티 중심의 객체지향적인 설계를 지향한다.
  • 상태 변경은 엔티티 내부 메서드에서 수행되도록 하고, repository는 저장/조회 역할에만 집중시킨다.
  • 연관관계 설정이나 연산은 서비스에서 직접 하지 않고, 의미 있는 메서드를 통해 위임하는 것이 유지보수에 유리하다.

6. 테스트 전략 및 유의사항

  • 단위 테스트는 핵심 로직의 의도를 기준으로 검증하며, 부수 결과까지 모두 assert할 필요는 없음
  • DB 상태를 확인하려면 @Commit을 붙여 트랜잭션을 커밋하거나 flush() 호출이 필요함
  • 테스트 환경의 DB URL과 콘솔에서 조회하는 DB URL이 같은지 반드시 확인해야 함 (H2는 tcp/embedded에 따라 분리될 수 있음)
  • 테스트를 반복 실행하면 중복 저장으로 충돌 가능성이 있으므로, 고유 값 또는 테스트 데이터 삭제 전략이 필요함