포토 스토리 페이지 구현
유저정보
사진정보
캡션정보
좋아요정보
댓글정보
2번으로 로그인 했다면 유저ID는 1,3(팔로하고 있는 사람)
SELECT * FROM image WHERE userId IN (1,3);
SELECT toUserId FROM subscribe WHERE fromUserId = 2;
이 2개를 서브쿼리 형태로 합친다.
SELECT * FROM image WHERE userId IN (SELECT toUserId FROM subscribe WHERE fromUserId = 2);
결과
1) api 만들기
유저 정보까지만 필요하지 User.java에서 images 정보를 또 가지고 올 필요가 없음. >> @JsonIgrnoreProperties()를 이용해서 images는 무시하도록 설정한다.
(1)
imagesRepository에서 쿼리를 만들 때, 2의 값은 로그인한 id >> principalId 이다.
SELECT * FROM image WHERE userId IN (SELECT toUserId FROM subscribe WHERE fromUserId = 2);
그럼 이걸 호출하는 api를 만들면 된다.
(2) Controller
(3) Service
26줄 : readOnly >> 읽기만 하는 것이므로, 영속성 컨텍스트가 변경을 감지해서 더티체킹하고 DB에 반영(flush)을 하지 않는다.
2) 뷰 랜더링하기
(1)JS에 ajax 만들기
12줄 : type은 get이지만 디폴트라서 굳이 넣지 않아도 된다.
17~8줄 : getStoryItem() 자체가 그림이고, 그 그림을 화면에 데이터 갯수만큼 forEach로 돌려서 뿌려주는 것이다.
27줄 : 위의 function을 실행해준다.
앞서 구독정보 구현의 subscribe.js에서 했던 것처럼, story.jsp에 있는 스토리가 구현되는 화면 코드를 가져와서 사용한다.
33줄 : 왜 image.user.profileImageUrl 일까?? image.java의 user는 fetch 타입이 Eager이기 때문에 이미지를 SELECT 하면 조인해서 User 정보를 같이 들고 오기 때문이다.
34줄 : 에러가 났을 때, 설정한 이미지를 띄운다.
2) 페이징하기
(1) 페이징
28줄 : @PageableDefault() : Pageable을 이용해서 페이징을 할 수 있다. size(한 화면에 뿌려지는 데이터 수)
28줄 : Pageable 은 data.domain.Pageable를 import 해야한다.
30줄 : 서비스에게 던진다.
이렇게 Service에서 Repository(정렬은 여기서 oreder by로 한다.)로 넘기면, 알아서 내부적으로 3건씩 최신순으로 id로 정렬해서 가지고 온다. (Page로 리턴받아야 한다!)
Postman으로 해보면, data 안 content 안에 이미지 데이터들이 있다는 것을 알 수 있다. >> JS에서 17줄에 중간에 content를 넣어줘야 화면에 뿌려진다.
(2) 스크롤
window의 scroll 이벤트
Window ScrollTop : 옆 스크롤바의 위치 계속 변함
= (Document height) - (Window height)
Document height : 고정값 전체 문서의 높이
Window height : 창의 크기 (컴퓨터 모니터 크기가 크면 더 커진다)
(Window ScrollTop) = (Document height) - (Window height) 이 공식이 맞아 떨어지는 지점을 찾으면 된다.
스크롤이 끝에 왔을 때, 값은 -1과 1 사이의 값이다.
93~6줄 : if문을 써서, checkNum의 값이 -1과 1사이의 값일 때, storyLoad()를 실행하게 해준다.
이때, 같은 스토리만 계속 보이면 안되므로 페이지가 넘어가도록 let page로 변수를 정의한다. 그리고 주소창에 페이지 값이 들어가게 한다.
94줄 : page++으로 페이지 값이 1씩 증가해서 로드하게 된다.
3) 좋아요 구현하기
(1) 좋아요 모델 만들기
각각의 이미지마다 toggleLikes가 작종되도록 id를 넣어서 개별적이게 만든다.
좋아요 에는 어떤 정보가 들어가야 할까? >> 어떤 '이미지'를 '누가' 좋아했는지에 대한 정보가 필요하다.
연관관계
이미지 좋아요
1 N 하나의 이미지는 여러개의 좋아요를 받을 수 있다.
좋아요 이미지
1 1 하나의 좋아요는 하나의 이미지에게만 줄 수 있다.
유저 좋아요
1 N 하나의 유저는 여러개의 좋아요를 할 수 있다.
(반대는 말이 안되므로 하지 않는다.)
모델을 만들어보면,
42, 46줄 : 위의 연관관계를 염두했을 때, Likes가 N image와 user가 1인 ManyToOne의 관계이다. 둘 다 FK로 JoinColumn을 해준다. (ManyToOne의 페치전략은 디폴트값이 Eager / OneToMany의 페치전략 디폴트는 Lazy)
28~31줄 : 유저1이 이미지1을 좋아요 눌렀는데 또 다시 좋아요를 누를 수는 없다. >> 중복 데이터 불가 >> 유니크 키로 묶는다.
(2) 좋아요 API 만들기
모델 만들면 >> Repository, Service, Controller 를 자동으로 만든다.
42줄 : 좋아요의 url은 어떻게 하는게 좋을까? 좋아요의 기능 >> 어떤 이미지를 좋아요 한다 >> api/image/{imageId}/likes
43줄 : PrincipalDetails >> 세션 정보를 의미한다. 여기서 로그인한 유저의 id를 뽑아온다.
서비스에서 아래의 방법은 비추천. (이유:set마다 각 데이터를 select 해와야 하기 때문에) >> Repository를 사용하도록 한다.
public void likes(int imageId, int principalId) {
Likes likes = new Likes();
likes.setUser();
likes.setImage();
}
LikesRepository
LikesService
45줄 : DB에 뭔가를 집어넣었다는 의미의 CREATED를 넣는다.(201번 코드)
(3) 좋아요 뷰 랜더링
좋아요 >> class="far-heart fas active"
좋아요 취소 >> class="fa-heart far"
이렇게 바뀜.
지금 좋아요를 했는지 안했는지에 대한 상태 의 정보가 필요하다
이 정보는 image 가져올 때, imageApiController에서 이미지서비스의 imageStory() 할때 images에 좋아요 상태를 담아가야 한다.
양방향 매핑이 필요하다. image.java에서 아래와 같은 변수를 선언한다.
이미지를 SELECT할 때, Likes의 getter를 호출하면 Likes 정보를 들고온다.
이미지 하나에 여러개의 좋아요가 가능하므로 @OneToMany이고 디폴트가 Lazy로딩, 나는 연관관계의 주인이 아니므로 FK 만들지 말라는 설정이다.(mappedBy="image"는 Likes.java의 image 변수이름이다.)
32줄 : 유저2로 로그인했을 때, 2가 팔로한 유저들의 이미지들 중 for문을 돌려서 이미지를 하나씩 뽑아낸다.
34줄 : 그 첫번째 이미지를 좋아요 하고 있는 정보를 다 가져온다.
35줄 : 그 좋아요가 유저2가 좋아요 한건지를 찾아낸다. >> Like.java의 user의 id와 principalId를 비교한다. 같다>>좋아요를 했다.>>좋아요상태 변수에 참을 넣어준다.
좋아요상태에 대한 변수를 image와 함께 가져가야하므로 image.java에 만드는데 DB에는 생기면 안됨 >> @Trasient (DB에 컬럼이 만들어지지 않는다)
ImageApiController의 imageStory()에서 images 리턴함
Image.java에서 Likes를 하게 되고
@OneToMany(mappedBy = "image")
private List<Likes> likes;
Likes.java에 가면 Images를 또 리턴하면서 무한참조가 일어난다.
User도 리턴해서 User.java에 가면 또 images를 리턴하게 된다.
@JoinColumn(name = "imageId")
@ManyToOne
private Image image;
@JoinColumn(name = "userId")
@ManyToOne
private User user;
무한참조 >> 해결하기
images를 리턴할 때, Likes를 리턴하게 된다. 이걸 막는다.
@JsonIgnoreProperties({"image"})
@OneToMany(mappedBy = "image")
private List<Likes> likes;
Story.js에서 중간에 if문을 걸고 값이 true면 하트가 active, false면 active하지 않게 설정한다.
Image.java에 좋아요상태처럼 좋아요카운트 변수를 선언하고, Service에서 image에 size()로 값을 넣어 js를 통해 화면에 뿌려주게 한다.
(4) 좋아요 구현 마무리(뷰와 api 이어주기)
far 빈 하트 >> 좋아요를 하겠다.
fas 빨간하트 >> 좋아요를 취소하겠다.
정상적으로 됐을 떄, fas를 넣고(112줄) far를 빼고(114줄) 액티브를 넣으면(113줄) >> 빨간 하트
정상적으로 됐을 떄, fas를 빼고고(126줄) 액티브를 빼고(127줄) far를 넣고(128줄) >> 빈 하트
<span class="like"><b id="storyLikeCount-${image.id}">${image.likeCount}</b>likes</span>
113줄 : 위 코드의 b태그 id인 storyLikeCount-${image.id}로 접근해서 .text()로 내부의 텍스트를 가져오겠다.
114줄 : 그 가져온 값에 +1 을 하는 것을 likeCount라고 하고
115줄 : +1된 값을 해당 b태그에 넣어준다.
좋아요 취소는 숫자만 -1로 바꿔주면 된다.
(5) 버그잡기
테이블 모두 비울 때, 연관관계가 있으면 테이블 데이터가 삭제가 안될 수도 있다. (User 테이블을 참고해서 Likes 테이블이 만들어질 때 등등) >> 자식부터 먼저 삭제한다.
user 정보 안에 Likes가 있고, Likes 안에 또 user 정보와 image 정보가 있다. image 정보 안에 Likes 정보가 또 있다.
쌀이 올린 사진에 좋아요를 눌러주면 무한참조로 오류가 발생하게 된다.
문제가 어디서 시작되는지를 알아보자
image를 select 했을 때는 문제가 없다. likes까지 괜찮음
그러나 user 안의 image가 튀어나오면서 문제 발생
Image >> Likes >> user 로 들어갈 때 오류가 터진다.
여기서 @JsonIgnoreProperties를 넣어 막아주면 무한참조가 안된다.
Likes에 image가 안나오게 된다. 해결!