Project/토이 프로젝트

[Cinemagram] Feed 페이지 렌더링 - (8)

Lea Hwang 2022. 12. 19. 01:11

이번 포스팅은 어떤 기능을 구현할까 고민이 있었습니다. 남은 기능 중 큰 꼭지인 팔로우 기능을 구현하고자 했지만

이번에는 조금 쉬운 기능을 구현하며 잠시 쉬어가는 챕터로 만들기로 결정했습니다. 

 

 

Feed 페이지 랜더링

Cinemagram의 Feed 페이지란?

로그인 한 유저가 팔로우한 유저들이 올린 이미지를 볼 수 있는 페이지입니다.

 

 

그럼 Feed 페이지로 갈 때 들고 가야 할 데이터는 어떤 게 있을까요?

  • User 정보
  • Image 정보
    • Image
    • caption
  • (좋아요, 댓글은 나중에 구현)

 

 

어떤 쿼리를 짜야하는가?

관련 Repository에 nativeQuery를 짜려고 하는데, 이때 어떤 쿼리를 넣어야 할까요?

 

❗❗ 당장 어떤 쿼리를 짜야할까 생각하지 말고 다음의 스텝을 따라가 보자 ❗❗
1. 과연 나는 어떤 걸 출력하길 원하는지
2. 1번의 결과를 얻기 위해서는 어떤 테이블, 어떤 칼럼들이 필요한지
3. 그럼 WHERE절에 어떤 쿼리가 들어가야 하는지

단계적으로 생각하고 쿼리를 하나씩 짜 보자

 

현재 팔로우 상황

 

fromUserId = 2에 주목해서 저 빨간 박스의 의미를 보면

"userId=2가 팔로우하고 있는 userId는 1번과 3번"이라는 뜻입니다. 

 

 

어떤 유저의 어떤 이미지를 출력할 것인가

  • 로그인 한 유저 자신이 올린 이미지는 Feed에서 볼 필요가 없다.
  • 팔로우한 유저들이 올린 이미지가 Feed에 나와야 한다.

 

 

여기까지 해서 어떤 데이터를 출력해야 할지 단계별로 알아보았습니다. 차근차근 쿼리를 작성해보도록 하겠습니다.

1. 로그인 한 유저(userId=2 가정)의 팔로우 정보 확인 후 팔로우한 유저 id를 리턴

* 우리는 아직 팔로우 기능 구현 전이므로, Postman으로 작업했습니다.

SELECT toUserId FROM follow WHERE fromUserId = 2;

 

2. 1,3번 유저가 올린 이미지를 출력

SELECT * FROM image WHERE userId IN (1,3);

 

 

3. 이제 1번, 2번 쿼리를 조합하면 됩니다. (서브 쿼리)

SELECT * FROM image WHERE userId IN (SELECT toUserId FROM follow WHERE fromUserId = 2);

 

 

 

쿼리 작성이 끝났습니다. 이제 이 쿼리를 작동시키기 위해서 Repository에 등록해보겠습니다.

ImageRepositry

public interface ImageRepository extends JpaRepository<Image, Integer> {

    @Query(value = "SELECT * FROM image WHERE userId IN (SELECT toUserId FROM follow WHERE fromUserId = :sessionId)", nativeQuery = true)
    List<Image> cFeed (@Param(value = "sessionId") int sessionId);

}

 

 

ImageRepository에 nativeQuery만 적으면 끝? 아니죠! 이를 호출하는 API 부분을 생성해보겠습니다.

우선 껍데기 부분만 만들어 놓고 다시 와서 채워보겠습니다.

ImageApiController

@RequiredArgsConstructor
@RestController
public class ImageApiController {

    @GetMapping("/api/feed")
    public ResponseEntity<?> Feed(){
        return null;
    }

}

 

Service 호출

ImageService

@Transactional(readOnly = true) 
public List<Image> Feed(int sessionId) {
    List<Image> images = imageRepository.cFeed(sessionId);
    return images;
}

 

Service부분을 만들었더니 Controller의 해당 함수에서 어떤 데이터를 넘기고, 결과로 받아야 하는지 알게되었습니다.

ImageApiController 추가

@GetMapping("/api/feed")
public ResponseEntity<?> Feed(@AuthenticationPrincipal CustomUserDetails customUserDetails){
    List<Image> images = imageService.Feed(customUserDetails.getUser().getId());
    return new ResponseEntity<>(new ResDto<>(1,"피드 성공", images), HttpStatus.OK);
}

 

확인

 

 

 

 

 

Pageable을 사용해 Pagination 구현

추가적으로, 스크롤을 내리면 Image가 이어서 나오는 페이징 기능도 구현해보도록 하겠습니다.

  • @PageableDefault() Pageable pageable
  • JS > window의 scroll이벤트
    • $(window). scroll(() => { });

 

 

Pageable

Pageable 참고 문서: 

https://docs.spring.io/spring-data/jpa/docs/1.7.2.RELEASE/reference/html/#_handlermethodargumentresolvers_for_pageable_and_sort

 

Spring Data JPA - Reference Documentation

Example 54. Combined Specifications MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR); List customers = customerRepository.findAll( where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount))); As you can see, Specifications offers some g

docs.spring.io

 

 

 

Pageable을 Controller 매개변수에 추가합니다.

ImageApiController

@GetMapping("/api/feed")
public ResponseEntity<?> Feed(@AuthenticationPrincipal CustomUserDetails customUserDetails,
                              @PageableDefault(size=4, sort="id", direction = Sort.Direction.DESC) Pageable pageable){
    Page<Image> images = imageService.Feed(customUserDetails.getUser().getId(), pageable);
    return new ResponseEntity<>(new ResDto<>(1,"피드 성공", images), HttpStatus.OK);
}
  • import 주의
    • import org.springframework.data.domain.Pageable;
  •  size=4
    • 한 번에 4개의 Image를 출력하겠다.
  • sort="id", direction = Sort.Direction.DESC
    • id를 기준으로 내림차순 정렬해서 출력하겠다. (최신순)

 

 

ImageService 호출

@Transactional(readOnly = true) 
public Page<Image> Feed(int sessionId, Pageable pageable) {
    Page<Image> images = imageRepository.cFeed(sessionId, pageable);
    return images;
}
  • @Transactional(readOnly = true) 
    • 읽기 전용
    • 영속성 콘텍스트에서 변경 감지 후 더티 체킹 해서 DB에 Flush(반영)하는 과정을 안 함 → 성능 향상

 

 

ImageRepository

public interface ImageRepository extends JpaRepository<Image, Integer> {

    @Query(value = "SELECT * FROM image WHERE userId IN (SELECT toUserId FROM follow WHERE fromUserId = :sessionId)", nativeQuery = true)
    Page<Image> cFeed (@Param(value = "sessionId") int sessionId, @Param(value = "pageable") Pageable pageable);

}

 

 

기존 코드에 Pageable 코드만 추가하였습니다. 그럼 어떤 식으로 가져오는 걸까요?

cFeed를 가지고 올 때 4건씩 Image를 id를 기준으로 최신순으로 정렬해서 가지고 옵니다.

 

 

 

size를 4로 설정했으므로 4까지의 이미지만 나옵니다.

 

 

 

window의 scroll이벤트

이제 window의 scroll이벤트를 통해

  • $(window).scroll(() => { });

스크롤 시 size에 맞는 Image를 추가적으로 더 로드해 볼텐데요.

$(window).scrollTop()

$(document).height()

$(window).height());

를 활용해서 

 

현재 스크롤 위치가 거의 0이 될 때, 다음 page를 로드하게끔 만들었습니다.

let checkNum = $(window).scrollTop() - ($(document).height() - $(window).height());

if(checkNum < 1 && checkNum > -1) {
    page++;
    storyLoad();
}

 

 

 

 

여기까지 Feed 페이지 렌더링 구현이 끝났습니다.  Feed 페이지 렌더링으로 push하였습니다.

이어서 Popular 페이지까지 구현하려고 했지만, 이 페이지는 좋아요를 받은 이미지를 출력하는 부분이므로 

 

다음 포스팅에서 진행하도록하겠습니다. 

  • 좋아요 기능 구현 후
  • Popular 페이지 랜더링