뉴스피드 프로젝트
MapStruct를 활용한 DTO 매핑
도메인 엔티티를 클라이언트 응답용 DTO로 변환하기 위해 MapStruct를 사용했다.
컴파일 시점에 안전한 매핑 코드를 자동 생성해주므로, 런타임 없이 빠르고 안정적인 DTO 변환이 가능하다
활용 예시
@Mapper(componentModel = "spring")
public interface PostMapper {
// PostRequest DTO -> Post Entity
Post toEntity(PostRequest postRequest, User user);
// Post Entity -> PostResponse DTO
@Mapping(source = "user", target = "author", qualifiedByName = "toAuthorResponse")
@Named("toResponse(Post)")
PostResponse toResponse(Post post);
// User Entity -> AuthorResponse DTO
@Named("toAuthorResponse")
AuthorResponse toAuthorResponse(User user);
// Page<Post> -> List<PostResponse>
@IterableMapping(qualifiedByName = "toResponse(Post)")
List<PostResponse> toListResponse(Page<Post> posts);
}
`@Mapper(componentModel = "spring")`: Spring Bean으로 등록되어 DI로 사용할 수 있게 한다.
`@Mapping`: `Post` 엔티티의 `user` 필드를 `PostResponse`의 `author` 필드로 매핑한다.
`@Named`: 매핑 메서드에 이름을 붙여 다른 매핑에서 참조할 수 있게 한다.
`qualifiedByName =`: 통해 `@Named` 로 지정된 이름의 메서드를 사용한다.
`@IterableMapping`: 컬렉션 타입의 매핑에 사용한다.
N+1 문제
N+1 문제란?
JPA에서 발생하는 성능 문제 중 하나로,
하나의 쿼리(N)로 조회한 엔티티가 연관된 엔티티를 지연 로딩(LAZY)하면서 추가 쿼리(+1)가 반복적으로 발생하는 현상
해결 방법 1: Fetch Join
JPQL에서 JOIN FETCH를 사용하여 연관된 엔티티를 즉시 로딩하는 방식.
한 번의 쿼리로 연관된 엔티티까지 모두 가져옴.
특징
- JPQL에서만 사용 가능
- 연관 엔티티를 SQL JOIN으로 함께 조회
- 성능 최적화에 효과적
- 컬렉션 페치 조인은 페이징 처리에 제약 있음
예시
@Query("""
SELECT c FROM Comment c
JOIN FETCH c.user
WHERE c.post.id = :postId
ORDER BY c.createdAt DESC
""")
List<Comment> findAllByPostIdWithUser(@Param("postId") Long postId);
해결 방법 2: EntityGraph
JPA의 @EntityGraph를 사용하여 특정 쿼리에서 연관 엔티티를 즉시 로딩하도록 명시하는 방식.
JPQL 없이도 메서드 레벨에서 설정 가능.
특징
- 메서드에 애노테이션으로 적용
- 페이징 처리에 유리함
- 복잡한 쿼리 없이 선언적으로 설정 가능
- 여러 연관 엔티티를 동시에 로딩 가능
예시
@EntityGraph(attributePaths = {"user"})
List<Comment> findAllByPostIdOrderByCreatedAtDesc(Long postId);
Fetch Join vs EntityGraph 비교
| 항목 | Fetch Join | EntityGraph |
| 사용 위치 | JPQL 내부 | Repository 메서드 |
| 선언 방식 | 명시적 JOIN FETCH | 애노테이션 기반 |
| 페이징 지원 | 컬렉션 페치 조인 시 불가 | 가능 |
| 복잡한 쿼리 | 유연하게 작성 가능 | 단순한 구조에 적합 |
| 유지보수 | 쿼리 변경 시 함께 수정 필요 | 선언적이라 유지보수 용이 |
| 성능 | 매우 우수 | 우수 (페이징에 강점) |
정리
- N+1 문제는 반드시 해결해야 할 성능 병목
- Fetch Join은 강력하지만 페이징에 주의
- EntityGraph는 선언적으로 깔끔하고 페이징에 적합
- 상황에 따라 둘을 병행하거나 선택적으로 사용하는 것이 좋음
마치며

대부분의 시간은 프로젝트의 오류 수정, 오타 정리, 부적절한 변수명 교체 등 리팩토링에 집중했다. 단순히 코드를 고치는 작업 같지만, 그 안에는 더 나은 구조와 더 명확한 의도를 향한 고민이 담겨 있었다. 오늘도 팀원들과 함께 끊임없이 상의하며, 더 좋은 방향으로 나아가기 위한 여정을 이어갔다. 그 과정에서 문득, 개발을 배우기 시작한 지 채 몇 달도 되지 않았는데 벌써 편협한 사고에 갇혀 있는 나 자신을 발견했다. 어떤 기능이 막히거나 구현이 어려워 보일 때, "이건 안 될 것 같다"는 판단을 너무 쉽게 내려버린 것이다. 내가 알지 못하는 수많은 방법들이 존재할 텐데, 왜 "어떻게 하면 가능할까?"라는 질문 대신, 내가 가진 작은 지식만으로 단정 지어버렸을까. 이런 사고는 개발뿐 아니라 어떤 분야에서도 성장을 막는 벽이 된다. 결국 내가 가진 지식은 전체의 아주 작은 조각일 뿐이고, 그 너머에는 가능성과 해결책이 존재한다는 걸 다시금 깨달았다. 중요한 건 정답을 아는 것이 아니라, 모르는 것을 인정하고, 질문을 던질 수 있는 용기다. 앞으로는 막막함 앞에서도 더 많이 질문하고 파고들며, 열린 마음으로 배워나가고 싶다. 개발은 결국 문제를 푸는 일이 아니라, 문제를 이해하고 함께 고민하는 과정이라는 걸 오늘도 팀원들과의 협업 속에서 배웠다.