Spring Boot

좋아요 수 표시하기

have a good time 2021. 11. 18. 20:02

이미지 위에 마우스를 가져다 대면, 그 이미지의 좋아요 수를 표시하는 코드를 완성하려고 한다.

 

이런식으로 좋아요 수가 2개이면 2가 표시되도록 하는 것이다.

 

이 페이지는 profile.jsp 에서 구현하고 있다.

그리고, 좋아요 수, 즉 likeCount 값은 Image.java에 있기 때문에 

이 값을 profile.jsp로 가져와서 사용하면 저렇게 표시를 할 수 있다.

 

그런데 문제는, 

 

<Image.java> 파일

	@Transient 
	private boolean likeState;
	
	@Transient
	private int likeCount;

여기 likeCount 가 @Transient 로, db에 값이 저장되지 않는 다는 것이다.

 

 

데이터 베이스에서 <Image db>를 살펴보면

 

 

이런식으로, likeCount값은 db에 저장되지 않는다.

그래서, image.getLikeCount(); 이런식으로

좋아요 수를 불러서 사용할 수 없다.

 

 

 

 

 

해결방법 :

UserService.java에서 좋아요 수를 추가해서 사용한다.

 

 

<UserService.java> 파일에 보면 

 

	
		User userEntity = userRepository.findById(pageUserId).orElseThrow(()-> {
			return new CustomException("해당 프로필 페이지는 없는 페이지입니다.");
		});
        
        	userEntity.getImages().forEach((image)->{
			image.setLikeCount(image.getLikes().size());
		});

userRepository.java로부터 사용자를 찾아와서 userEntity라는 객체에 저장하고,

이 userEntity 객체로부터 image 객체의 likeCount에 값을 넣어주는 것을 알 수 있다.

자세히 살펴보면,

 

 

<User.java> 파일

 

private List<Image> images;

<Image.java> 파일

 

	@Transient 
	private boolean likeState;
	
	@Transient
	private int likeCount;
    
    
    	@JsonIgnoreProperties({"image"})
	@OneToMany(mappedBy = "image") 
	private List<Likes> likes;

(User.java에 image 객체가 있으므로 userEntity.getImages()로  Image객체의 likeCount 변수를 사용할 수 있다.)

 

그리고 Image.java에 likes 변수가 있으므로,

image.getLikes().size()로 좋아요 수를 정하면 된다.

 

<Likes.java> 파일

 

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(
		uniqueConstraints = {
				@UniqueConstraint(
						name="likes_uk",
						columnNames = {"imageId", "userId"}
				)
		}
)
public class Likes {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	@JoinColumn(name = "imageId")
	@ManyToOne
	private Image image; 
	
	@JsonIgnoreProperties({"images"})
	@JoinColumn(name = "userId")
	@ManyToOne
	private User user; 
	
	private LocalDateTime createDate;
	
	@PrePersist
	public void createDate() {
		this.createDate = LocalDateTime.now();
	}


}

 

 

 

그런다음 

UserController.java에서 이 값을 model에 담아서 Profile.jsp 파일에 뿌려주면 된다.

 

<UserController.java> 파일

 

@GetMapping("/user/{pageUserId}")
	public String profile(@PathVariable int pageUserId, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
		UserProfileDto dto = userService.회원프로필(pageUserId, principalDetails.getUser().getId());
		model.addAttribute("dto", dto);
		return "user/profile";
	}

* 여기서 보면 userService.java로 부터 받은 값을 UserProfileDto 객체로 받고 있는데, 이는 아래에서 더 설명하겠다.

일단은 UserProfileDto dto 객체를 통해서 likeCount 값을 얻을 수 있어서, dto를 model에 담아서 profile.jsp 파일에 전달해준다고만 생각하면 된다.

 

 

<Profile.jsp> 파일

	<c:forEach var="image" items="${dto.user.images}">
		<div class="img-box">
			<a href=""> <img src="/upload/${image.postImageUrl}" />
			</a>
			<div class="comment">
				<a href="#" class=""> <i class="fas fa-heart"></i><span>${image.likeCount}</span>
				</a>
			</div>
		</div>
	</c:forEach>

 

이렇게, UserController.java로부터 받은 dto를 이용해서

dto.user.images 값을 image 라는 변수 이름으로 지정하여

${image.likeCount} 로 활용하고 있다.

이렇게 하면 좋아요 수 표시가 완성된다.

 

 

 

이렇게, @Transient 즉, db에 저장되지 않는 변수를 이용하려면

service.java 에서 이를 구현해서 set으로 넣어주고 활용하면 된다.

 

원래 

Image.java 파일의

 

	@Transient 
	private boolean likeState;
	
	@Transient
	private int likeCount;

 

이 두개의 값은 사이트 첫 화면에 이미지 좋아요 수를 표시하려고 했던 건데,

여기 역시 ImageService.java에서 보면 Image.java의 다른 값들(id, caption, createDate 등)은

ImageRepository.java에서 가져오지만, 

likeState, likeCount는 직접 구현해서 set 하고 있다.

 

<Image db> 

 

<ImageService.java> 파일

	@Transactional(readOnly = true) 
	public Page<Image> 이미지스토리(int principalId, Pageable pageable){
		Page<Image> images = imageRepository.mStory(principalId, pageable);
		
		//***********
		images.forEach((image)->{
			
			image.setLikeCount(image.getLikes().size());
			
			image.getLikes().forEach((like) -> {
				if(like.getUser().getId() == principalId) { 
					image.setLikeState(true);
				}
			});
			
		});
             //********
		
		return images;
	}

//*********

한 부분을 보면, service.java에서 직접 likeCount, likeState 를 구해서 set하고 있음을 알 수 있다.

 

 

-끝-

------------------------------------------------------

 

 

 

그러면 이제는 위에서 설명했던, UserProfileDto.java에 대해서 설명해보겠다.

위에서 UserService.java에서 UserProfileDto dto 객체를 return 하고, 이를 UserController.java -> Profile.jsp 에 전달해서

좋아요 수를 화면에 표시한다고 했다.

 

그런데 의문이 들 수 있다.

 

<UserService.java> 파일

	@Transactional(readOnly = true)
	public UserProfileDto 회원프로필(int pageUserId, int principalId) {
		UserProfileDto dto = new UserProfileDto(); 
		
		User userEntity = userRepository.findById(pageUserId).orElseThrow(()-> {
			return new CustomException("해당 프로필 페이지는 없는 페이지입니다.");
		});
		
		dto.setUser(userEntity);
		dto.setPageOwnerState(pageUserId == principalId);
		dto.setImageCount(userEntity.getImages().size());
		
		int subscribeState =  subscribeRepository.mSubscribeState(principalId, pageUserId);
		int subscribeCount = subscribeRepository.mSubscribeCount(pageUserId);
		
		dto.setSubscribeState(subscribeState == 1);
		dto.setSubscribeCount(subscribeCount);
		
		// 좋아요 카운트 추가
		userEntity.getImages().forEach((image)->{
			image.setLikeCount(image.getLikes().size());
		});
		
		return dto;
	}

여기서 보면, dto에 값들을 담아서 UserController.java에 전달한다.

 

<UserController.java> 파일

	@GetMapping("/user/{pageUserId}")
	public String profile(@PathVariable int pageUserId, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
		UserProfileDto dto = userService.회원프로필(pageUserId, principalDetails.getUser().getId());
		model.addAttribute("dto", dto);
		return "user/profile";
	}

그래서 model에 이 dto를 Profile.jsp 파일에 전달 한 뒤 이를 활용하는데,

그렇다면 그냥 좋아요 수도 위의 UserService.java 파일에서 

 

		userEntity.getImages().forEach((image)->{
			image.setLikeCount(image.getLikes().size());
		});

이런식으로 따로 만들지 말고 UserProfileDto dto 객체에 저장하면 안되나?

이렇게 따로 만들었기 때문에, 좋아요 수 (likeCount)는 dto에 저장된 값이 아니라,

dto에 저장된 user 객체에서 image객체를 불러와서 ( -> dto.getUser().getImages() )

image객체의 likeCount 변수를 사용해야 한다.

 

 

그래서

<Profile.jsp 파일>을 보면

	<c:forEach var="image" items="${dto.user.images}"> 
		<div class="img-box">
			<a href=""> <img src="/upload/${image.postImageUrl}" />
			</a>
			<div class="comment">
				<a href="#" class=""> <i class="fas fa-heart"></i><span>${image.likeCount}</span>
				</a>
			</div>
		</div>
	</c:forEach>

이런식으로, var="image", items = ${dto.user.images} 로 불러와서

<span>${image.likeCount}</span>으로 사용하고 있음을 알 수 있다.

 

하지만 좋아요 수를 dto 객체에 저장한다면

 

var="dto", items = ${dto},

<span>${dto.likeCount}</span> 으로 바로 사용할 수 있을 것이다.

 

그렇다면 왜 이렇게 하지 않는가?

 

UserProfileDto.java 파일을 살펴보겠다.

 

 

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserProfileDto {
	private boolean pageOwnerState;
	private int imageCount;
	private boolean subscribeState;
	private int subscribeCount;
	private User user;
}

 

이 값들은 결국 Profile.jsp 파일에 사용하기 위해 만들어졌다.

즉 프로필을 보여주는 페이지이다.

여기서 UserProfileDto.java의 변수

pageOwnerState, imageCount 값들을 사용하려고 만든 것이다.

그런데 여기에 likeCount 변수가 추가된다면 어떻게 될까? 안된다. 

왜그런지 살펴보면,

 

프로필 페이지마다 필요한 pageOwnerState, imageCount 등 변수에 해당하는 값은 1개이다.

그래서 예를 들면

 

pageOwnerState = true

imageCount =1

subscribeState = true

subscribeCount = 2

user 객체

 

이런식으로 값들이 1개만 있으면 된다.

그런데 문제는, 프로필 페이지에 이미지가 여러개 나온다는 것이다.

(현재 인스타그램 클론코딩으로, 사용자가 올린 여러 이미지가 프로필 페이지에 올라오도록 설정했다.)

 

여기에 likeCount가 추가되면,

이미지가 여러개인데, likeCount 값을 무엇으로 정할 수 있을까 ? 없다.

A이미지 좋아요 수 : 1,

B 이미지 좋아요 수 : 2 

 

이렇게 정해졌다면,

likeCount 를 1 또는 2 중에서 고르는 게 아니라

1,2 모두 필요하다.

 

그래서 각 이미지마다 좋아요 수를 구하도록 

 

<UserService.java> 파일에서

		userEntity.getImages().forEach((image)->{
			image.setLikeCount(image.getLikes().size());
		});

이런식으로 따로 구해놓고, 각 이미지마다 좋아요 수를 image 객체의 likeCount 변수에 저장하는 것이다.

(이 값은 결과적으로 dto에 저장되는 게 아니다. image 객체의 likeCount 변수에 set되는 것임)

 

 

-끝-

 

참고 자료 : 이지업 강의 사이트 "스프링부트 SNS 프로젝트 - 포토그램 만들기"