1. 프로젝트 구조
src/main/java/com/example/withpeace/
│
├── domain/ # 도메인 모델 (엔티티)
│ ├── Image.java # 이미지 엔티티
│ └── Post.java # 게시글 엔티티
│
├── repository/ # 데이터 접근 계층
│ ├── ImageRepository.java # 이미지 레포지토리
│ └── PostRepository.java # 게시글 레포지토리
│
├── dto/ # 데이터 전송 객체
│ └── response/
│ └── PostListResponseDto.java # 게시글 리스트 조회 응답 DTO
│
├── controller/ # 컨트롤러 계층
│ └── PostController.java # 게시글 관련 API 엔드포인트
│
├── service/ # 비즈니스 로직 계층
│ └── PostService.java # 게시글 관련 비즈니스 로직
│
└── util/ # 유틸리티 클래스
└── TimeFormatter.java # 시간 포맷팅 유틸리티
2. DTO 구현
@Builder
public record PostListResponseDto(
Long postId,
String title,
String content,
ETopic type,
String createDate,
String postImageUrl) {
// 정적 팩토리 메서드: Post 엔티티와 이미지 URL을 받아 DTO 객체 생성
public static PostListResponseDto from(Post post, String postImageUrl) {
return new PostListResponseDto(
post.getId(),
post.getTitle(),
post.getContent(),
post.getType(),
TimeFormatter.timeFormat(post.getCreateDate()), // 날짜 포맷팅
postImageUrl
);
}
}
게시글 리스트 조회의 응답 DTO 이다. 게시글의 정보와 게시글과 연관된 이미지 1개를 반환한다.
from 메서드
Post 엔티티와 이미지 URL을 받아 DTO 객체를 생성하는 *정적 팩토리 메서드 이다. 서비스 계층의 가독성을 높이기 위해 사용했다.
정적 팩토리 메서드 (Static Factory Method)
REF) https://inpa.tistory.com/entry/GOF-💠-정적-팩토리-메서드-생성자-대신-사용하자
정적 팩토리 메서드 패턴은 개발자가 구성한 Static Method를 통해 간접적으로 객체를 생성하는 디자인 패턴이다. 객체를 인스턴화 할 때 생성자를 호출하여 생성하는 것이 아닌, 메서드를 호출하여 객체를 생성하는 방법이다.
정적 팩토리 메서드는 생성자의 역할을 대신 이행하는 것 뿐만 아니라, 좀 더 가독성 좋은 코드를 작성하고 객체지향적으로 프로그래밍 할 수 있게 도와준다.
3. 레포지토리 구현
3.1. PostRepository
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
@Query(value = "SELECT p FROM Post p WHERE p.type=:type ORDER BY p.createDate DESC")
Page<Post> findByType(ETopic type, Pageable pageable);
}
findByType은 특정 타입의 게시글을 페이지네이션하여 조회하는 메서드이다. ORDER BY p.createDate DESC로 생성시간 기준으로 내림차순 정렬했다.
3.2. ImageRepository
@Repository
public interface ImageRepository extends JpaRepository<Image, Long> {
// ...
@Query(value = "SELECT i.url FROM images i WHERE i.post_id = :postId ORDER BY i.id ASC LIMIT 1",
nativeQuery = true)
Optional<String> findUrlsByPostIdOrderByIdAsc(@Param("postId") Long postId);
}
findUrlsByPostIdOrderByIdAsc은 특정 게시글의 첫 번째 이미지 URL을 조회하는 메서드이다.
LIMIT 1로 첫 번째 결과만 가져오기 위해 nativeQuery 속성을 사용하였다. JPA 쿼리 메서드로 표현하기 어렵기 때문이다.
Optional<String>을 반환 타입으로 설정했다.
- NullPointerException 방지
- 값의 부재 명시적으로 표현
4. 컨트롤러 구현
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
public class PostController {
private final PostService postService;
// ... (게시글 관련 메서드 생략)
// 게시글 리스트 조회
@GetMapping("")
public ResponseDto<?> getPostList(@UserId Long userId, // 사용자 ID
@RequestParam ETopic type, // 게시글 타입
@RequestParam(defaultValue = "0") @Valid @NotNull @Min(0) Integer pageIndex, // 페이지 인덱스
@RequestParam(defaultValue = "1") @Valid @NotNull @Min(1) Integer pageSize // 페이지 크기
) {
return ResponseDto.ok(postService.getPostList(userId, type, pageIndex, pageSize));
}
}
사용자 ID, 게시글 타입, 페이지 정보를 입력받아 서비스 계층에 전달하고, 결과를 반환한다.
- @RequestParam: HTTP 요청의 파라미터를 메서드 파라미터에 바인딩한다.
- @Valid: 해당 파라미터의 유효성 검사를 수행한다.
- @NotNull: 파라미터가 null 이 아님을 검증한다.
- @Min: 파라미터의 최소값을 지정한다.
5. 서비스 구현
@Service
@RequiredArgsConstructor
public class PostService {
// ... (필드 선언 생략)
// ... (게시글 관련 메서드 생략)
@Transactional
public List<PostListResponseDto> getPostList(Long userId, ETopic type, Integer pageIndex, Integer pageSize) {
// 사용자 존재 여부 확인
User user =
userRepository.findById(userId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_USER));
Pageable pageable = PageRequest.of(pageIndex, pageSize); // 페이지네이션 객체 생성
Page<Post> postPage = postRepository.findByType(type, pageable); // 게시글 조회
// 게시글 목록을 DTO로 변환
List<PostListResponseDto> postListResponseDtos = postPage.getContent().stream()
.map(post -> {
// 각 게시글의 첫 번째 이미지 URL 조회
String postImageUrl = imageRepository.findUrlsByPostIdOrderByIdAsc(post.getId())
.orElse(null); // 이미지가 존재하지 않으면 null
// DTO 생성 및 반환
return PostListResponseDto.from(post, postImageUrl);
})
.collect(Collectors.toList()); // 게시글이 존재하지 않으면 빈 리스트
return postListResponseDtos;
}
}
- 사용자 ID를 통해 사용자를 조회하고 존재하지 않으면 에러를 반환한다.
- Pageable 객체를 생성해 페이지네이션 정보를 설정한다.
- 특정 타입의 게시글을 페이지네이션하여 조회한다.
- 조회된 게시글 목록을 stream 을 사용해 DTO로 변환한다.
- 각 게시글에 대해 첫 번째 이미지 URL을 조회한다.
- 게시글 정보와 이미지 URL을 사용해 DTO를 생성한다.
- 변환된 DTO 리스트를 반환한다.
6. API 응답 예시
요청 URL
[GET] http://cheongha.site/api/v1/posts?type=FREEDOM&pageIndex=0&pageSize=10
Response Body
{
"data": [
{
"postId": 8,
"title": "가을이 온 것 같네요!",
"content": "선선한 날씨가 너무 좋네요!\\n시원해진 날씨에 다들 하고싶은게 있으신가요?!",
"type": "FREEDOM",
"createDate": "2024/09/22 17:37:07",
"postImageUrl": null
},
{
"postId": 1,
"title": "너무 덥네요ㅠ",
"content": "ㅠㅠ",
"type": "FREEDOM",
"createDate": "2024/07/17 22:23:58",
"postImageUrl": "https://storage.googleapis.com/cheong-ha-bucket/postImage/1/0_imageFile2488896157096969460.jpg"
}
],
"error": null
}
반응형
'Projects > 청하-청년을 위한 커뮤니티 서비스' 카테고리의 다른 글
[청하] 12. 게시글 수정 기능 구현 (1) | 2024.10.09 |
---|---|
[청하] 11. 게시글 삭제 기능 구현 (feat. NCP Object Storage 이미지 삭제) (2) | 2024.10.09 |
[청하] 9. 게시글 createDate 포멧 변경 (feat. DateTimeFormatter) (1) | 2024.10.08 |
[청하] 8. 게시글 상세조회 기능 구현 (feat. LocalDateTime) (0) | 2024.10.07 |
[청하] 7. 게시글 등록 기능 - 인가 권한 설정 (0) | 2024.10.07 |
댓글