-
스프링부트+JPA - 인스타그램 클론 코딩 12 - 기타(인기페이지/좋아요카운트/프로필 유저 사진 변경)스프링/스프링부트+JPA - 인스타 2021. 10. 18. 19:27
이지업 최주호 강사님 강의 참조
01 인기페이지 구현(좋아요가 높은순으로 출력)
이 화면에서 이제 좋아요 순으로 나열할 예정 - 저 페이지를 호출하는 컨트롤러에서 데이터를 들고 호출해야한다.
APIController에 안만드냐는 질문이 있을 수 있다. 모든 API 컨트롤러는 @RestController가 달려있고 데이터를 리턴하는 컨트롤러들을 구현해놓은 것이다. 보통 이 리턴받은 데이터로 AJAX등을 구현하기위해 API를 쓴다. 현재는 단순 데이터를 들고 호출 + JSP를 리턴하는것이기때문에 API에서 세팅을 하지 않는다. (API로 구현이 가능하기는 하다. 하지만 이런 단순 호출에 데이터를 리턴하면 JS를 써야하고 쓸데없이 어렵게 구현해야하는 번거로움이 있다. 또는 안디로이드/IOS등에서 요청하는것은 HTML로 못주고 데이터로 주기때문에 반드시 API로 해야한다.)public class ImageController { ... @GetMapping("/image/popular") public String popular(Model model) { List<Image> images = imageService.popularPics(); model.addAttribute("images", images); return "image/popular"; } ...
- imageService.popularPics() 추가
public class ImageService { ... //인기페이지 호출시 좋아요 순 @Transactional(readOnly = true) public List<Image> popularPics(){ return imageRepository.mPopular(); }
- imageRepository.mPopular() 추가
- 레파지토리에서 nativeQuery를 구현하기전에 직접 연습을 해보는 과정이 중요하다
먼저 image/likes의 DB 상태는 아래와 같다image likes - 여기서 image를 뽑는데 좋아요가 된 이미지만 뽑는 쿼리를 보자
SELECT imageId FROM likes; SELECT * FROM image WHERE id IN(SELECT distinct imageId FROM likes);
중복을 제거하고 하나씩 들고오게되지만 보시다싶이 3이 더 많은데 2,3 순으로 들고온다. 즉 distinct는 안된다는 말 - 가상컬럼 + GROUP BY로 처리해주자
SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId ORDER BY likeCount DESC;
카운터 + 그룹화로인해서 중복된거는 카운터처리되서 어느게 얼마나 가지고 있는지를 볼 수 있다.
그 이후 오더바이로 처리하면 원하는 쿼리가 나온다. - 마지막으로 인라인뷰를 써서 테이블을 하나로 만들고 IN에 넣어주면 되는데 IN의 특성상 정렬이 되지 않기때문에 3번의 쿼리를 조인시키고 아이디와 image아이디를 같은것으로 테이블을 만들고 likeCount로 정렬하면 된다.
SELECT * FROM image i INNER JOIN (SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId) c ON i.id = c.imageId ORDER BY likeCount DESC;
- 이제 4번의 쿼리를 레퍼지토리에 넣으면 되는데 이미지를 호출할때 Image.java 클래스는 imageId + likeCount를 들고 있지 않기때문에 오류가 발생한다. 그래서 표현식을 좀 바꿔 i 값만 들고오면 된다(i값만 들고와도 4번에서 우리가 구하고자하는 좋아요 순으로 뽑힌다)
모든 테이블이 아닌 i.*을 써서 image의 id와 관련된 테이블만 들고 오도록 변경SELECT i.* FROM image i INNER JOIN (SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId) c ON i.id = c.imageId ORDER BY likeCount DESC;
- 레파지토리에 넣어보자
public interface ImageRepository extends JpaRepository<Image, Integer>{ .. @Query(value = "SELECT i.* FROM image i INNER JOIN(SELECT imageId, COUNT(imageId) likeCount FROM likes GROUP BY imageId) c ON i.id = c.imageId ORDER BY likeCount DESC", nativeQuery = true) List<Image> mPopular(); }
- popular.jsp에 뿌리기
<!--인기 게시글--> <main class="popular"> <div class="exploreContainer"> <!--인기게시글 갤러리(GRID배치)--> <div class="popular-gallery"> <c:forEach var="image" items="${images}"> <div class="p-img-box"> <a href="/user/${image.user.id}"> <img src="/upload/${image.postImageUrl}" /> </a> </div> </c:forEach> </div> </div> </main> <%@ include file="../layout/footer.jsp"%>
각각의 사진을 클릭하면 이 사진을 올린 유저로 이동 된다!
더보기여기에서 <img src="/upload/${image.postImageUrl}" /> 을 들고온다 <a href="/user/${image.user.id}"> -> 이미지를 올린 유저로 이동
02 프로필페이지 좋아요 카운트
- 이미 좋아요를 구현했기때문에 그 구현한 것에 값을 그대로 들고와서 뿌리기만 하면 된다.
- 유저 프로필이기때문에 User를 호출하면 User.java가 들고있는 Image가 불러와지고 그 Image.java가 들고있는 likeCount를 그대로 쓰면 된다
- 서비스에서 호출시에 카운터를 넣어서 계산이 되도록 하고
public class UserService { ... @Transactional(readOnly = true) public UserProfileDto profile(int pageUserId, int principalId) { ... userEntity.getImages().forEach((image)->{ image.setLikeCount(image.getLikes().size()); }); return dto; }
- JSP에서 받아주면 된다.
<a href="#" class=""> <i class="fas fa-heart"></i><span>${image.likeCount}</span>
번외로 그냥 외부(JSP)에서 계산을 해도 된다
더보기<a href="#" class=""> <i class="fas fa-heart"></i><span>${image.likes.size()}</span>
03 프로필 사진 수정
동그라미 안에 이미지를 바꿔볼 예정 구현해야할 기능은 두가지 이다.
1) 유저가 로그인해서 이미지를 바꾸면 그 바꾼 이미지로 등록 되도록.
2) 1번으로 로그인했는데 2번으로 들어와서 1번을 못바꾸도록 막기.
- 현재 저 사진이미지를 클릭하면 사진을 변경할 수 있도록 되어있다. 그 코드는 아래와 같다
profile.jsp → 유저 이미지를 클릭하게되면 저 부분이 발동하게 되고 'id="userProfileImageInput"' 함수가 아래와 같이 발동 된다
함수가 발동되고 이미지는 변하지면 새로고침하면 원상복구 된다. 여기서 이제 DB에 넣는 과정을 구현 할 예정
profile.js DB에 넣을때 막넣으면 안되고 로그인 유저와 페이지 유저가 같아야만 이미지가 변경이 되도록하고 그 값이 DB로 들어가야 한다.
- profile.js 에서 파라미터 받기
- JSP에서 저 두 파라미터를 날리도록 세팅
로그인 유저는 쉽게 들고 오지만 유저의 아이디를 어떻게 들고올지를 모르겠으면 위에 코드가 어떻게 뿌려지는지 보면 쉽게 찾을 수 있다! - → 테스트
3. 저 두값을 비교 해서 값이 다르면 아예 이미지 업로드가 안뜨도록 세팅
4. 이제 같은유저이면 수정이 되고 수정된 데이터가 (이미지)를 서버에 전송해줘야 한다.
- 변경된 이미지는 위의 폼 태그에 담긴다
- profile.js에서 서버에 바뀐 이미지를 전송할 코드를 구성 하자
0번째를 들고 와야하는걸 볼 수 있다. 0번째 이 데이터를 보내야한다. - 이제 위에서 축출된 데이터를 보내야하는데 보통 ajax를 쓰지 않으면 submit으로 보내면 되지만 현재 ajax로 처리를 하기 때문에 데이터를 form 객체로 감싸야 한다.
- 이제 이 코드를 실행할 API 구축 (UserApiController)
@PutMapping("/api/user/{principalId}/profileImageUrl") public ResponseEntity<?> profileImageUrlUpdate(@PathVariable int principalId, MultipartFile profileImageFile, @AuthenticationPrincipal PrincipalDetails principalDetails){ User userEntity = userService.changeProfileImage(principalId,profileImageFile); principalDetails.setUser(userEntity); return new ResponseEntity<>(new CMRespDto<>(1, "프로필 이미지 변경 성공", null),HttpStatus.OK); }
- 호출시에 principalId와 파일내용을 같이 넘겨줘야하므로 파라미터에 principalId/profileImageFile를 받고 거기에 세션체크를 해야함으로 @AuthenticationPrincipal PrincipalDetails principalDetails까지 넣어 주자
- 파일에 대한 파라미터 이름은 input되는 곳에서 선언된 "name"과 반드시 같아야 오류가 없다
- 사진 변경 후 변경된 사진 및 회원의 세션이 똑같이 변경 되야 하므로 서비스 호출을 객체화 시키고 그 바뀐 호출 객체로 세션을 바꿔주면 되는 것.
- UserService에 함수 추가
public class UserService { .. @Value("${file.path}") private String uploadFolder; @Transactional public User changeProfileImage(int principalId, MultipartFile profileImageFile) { UUID uuid = UUID.randomUUID(); String imageFileName = uuid+"_"+profileImageFile.getOriginalFilename(); //실제 파일 이름 // System.out.println("파일 이름: "+imageFileName); Path imageFilePath = Paths.get(uploadFolder+imageFileName); try { Files.write(imageFilePath, profileImageFile.getBytes()); } catch (Exception e) { e.printStackTrace(); } User userEntity = userRepository.findById(principalId).orElseThrow(()->{ throw new CustomApiException("유저를 찾을 수 없습니다."); }); userEntity.setProfileImageUrl(imageFileName); return userEntity; }
→ try/catch까지 정상적으로 타면 사진은 서버에 저장이 된다. 그 이후 서버에 저장된 사진을 DB에 넣는 작업을 해줘야 한다. 첫 User UserEntity로 영속화를 시킨 후 맨 마지막 문장에서 'userEntity.setProfileImageUrl(imageFileName);'로 DB값을 바꾸면 된다.
- JSP에 마지막으로 호출
굿!
'스프링 > 스프링부트+JPA - 인스타' 카테고리의 다른 글
스프링부트+JPA - 인스타그램 클론 코딩 12 - 1 - 댓글(삭제/유효성 검사) (0) 2021.10.19 스프링부트+JPA - 인스타그램 클론 코딩 12 - 댓글(모델링/삽입) (2) 2021.10.19 스프링부트+JPA - 인스타그램 클론 코딩 11 - 좋아요 (무한참조 버그 잡기) (0) 2021.10.08 스프링부트+JPA - 인스타그램 클론 코딩 10 - 좋아요 (API/렌더링/카운팅) (0) 2021.10.08 스프링부트+JPA - 인스타그램 클론 코딩 09 - 스토리 페이지(API/렌더링/페이징) (0) 2021.10.08 - 저 페이지를 호출하는 컨트롤러에서 데이터를 들고 호출해야한다.