Spring Boot

[무한댓글 게시판] 6. 조회수 / 좋아요

어코링 2024. 2. 22. 15:37

조회수

게시글의 상세페이지로  이동하는 동시에 조회수가 올라가야하는 거니까 게시글 상세 이동 메서드에 로직을 추가해줬다.

동영상 시연

좋아요

좋아요 기능구현을 하기 위해 좋아요의 상태를 담기위해 따로 테이블이 필요할 것 같아서 likes 테이블 설계를 하고 테이브블 생성을 해주었다.

 

create table likes(
   useremail varchar(25),
    board_id int,
    PRIMARY KEY (useremail, board_id)
);

 

likes 테이블은 단일 식별자가 아닌 user 테이블의 PK useremail과 board 테이블의 PK board_id 값을 Likes 테이블을 복합키로 설정해주었다.

 

Likes.java

Likes entity

 

package com.se.social.entity;

import java.io.Serializable;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.se.social.domain.LikesId;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "likes")
@Data
@AllArgsConstructor
@NoArgsConstructor
@IdClass(LikesId.class)
public class Likes implements Serializable {


    @Id
    private String useremail;

    @Id
    @Column(name = "board_id")
    private int board_id;
}

LikesId.java

LikesId domain

package com.se.social.domain;

import java.io.Serializable;

import javax.persistence.Id;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LikesId implements Serializable {
	private static final long serialVersionUID = 1L; // 기본값(초기화)
	
	private String useremail;

	private int board_id;
}

LikesRepository.java

LikesRepository도 생성해주었다.

package com.se.social.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;

import com.se.social.domain.LikesId;
import com.se.social.entity.Likes;
import com.se.social.entity.QLikes;
import com.querydsl.jpa.impl.JPAQueryFactory;

public interface LikesRepository extends JpaRepository<Likes, LikesId> {

}

 

LikesService.java

package com.se.social.service;

import com.se.social.domain.LikesId;
import com.se.social.entity.Likes;

public interface LikesService {
	// selectDetail
	public Likes selectDetail(LikesId likesId);

	// insert, update
	public int save(Likes entity);

	// delete
	public LikesId delete(LikesId likesId);


}

LikesService.java

package com.se.social.service;

import org.springframework.stereotype.Service;

import com.se.social.domain.LikesId;
import com.se.social.entity.Likes;
import com.se.social.repository.LikesRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class LikesServiceImpl implements LikesService {

	private final LikesRepository repository;

	// selectDetail
	@Override
	public Likes selectDetail(LikesId likesId) {
        return repository.findById(likesId).orElse(null);
    }
	
	// insert, update
	@Override
	public int save(Likes entity) {
		repository.save(entity);
		return entity.getBoard_id();
	}

	// delete
	@Override
	public LikesId delete(LikesId likesId) {
		repository.deleteById(likesId);
		return likesId;

	}
}

LikesService와 LikesServiceImpl을 만들어주었고,

여기서 주요하게 봐야할 점은 . selectDetail 메서드.

selectDetail 메서드를 생성할때,Likes 타입으로 만들어주고 타입도 같은 타입으로 반환해준다.

매개변수는 복합키를 만들어주었던 LikesId를 받아서 복합키를 찾아서 반환해준다. 없다면 null을 반환.

BoardController.java

// 좋아요
	@PostMapping("/toggle")
	@ResponseBody
	public ResponseEntity<?> toggleLike(@RequestBody Likes entity) {
	    try {
	        LikesId id = new LikesId(entity.getUseremail(), entity.getBoard_id());
	        Board boardEntity = boardService.selectDetail(entity.getBoard_id());

	        if (boardEntity != null) {
	            Likes existingLike = likesService.selectDetail(id);

	            if (existingLike == null) {
	                // 좋아요 추가
	                boardEntity.setBoard_likes(boardEntity.getBoard_likes() + 1);
	                likesService.save(entity);
	                boardService.save(boardEntity); // 트랜잭션 처리

	                System.out.println("좋아요 추가 완료. 현재 좋아요 개수: " + boardEntity.getBoard_likes());

	                return ResponseEntity.ok().build();
	            } else {
	                // 좋아요 취소
	                boardEntity.setBoard_likes(boardEntity.getBoard_likes() - 1);
	                likesService.delete(id);
	                boardService.save(boardEntity); // 트랜잭션 처리

	                System.out.println("좋아요 취소 완료. 현재 좋아요 개수: " + boardEntity.getBoard_likes());

	                return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
	            }
	        } else {
	            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("해당 게시글을 찾을 수 없습니다.");
	        }
	    } catch (Exception e) {
	        e.printStackTrace();
	        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(false);
	    }
	}

 

다음은 controller에 토글 방식으로 만들었던, 좋아요 기능.

새로운 useremail과 board_id 값으로 좋아요를 누르면 likes 테이블에 데이터 추가,

또 같은 useremail과 board_id 값으로 좋아요를 누른 상태에서 한번더 누르게 된다면 likes 테이블에서 데이터가 제거 

⮕ likes 테이블을 복합키로 구성한 이유

 

BoardDetail.jsp

<div class="likes">
			<span id="likeButton" style="cursor:pointer;" onclick="toggleLikes(${requestScope.boardDetail.board_id},'${sessionScope.loginUser.useremail}')">❤︎</span>
			<span id="likeCount">${requestScope.boardDetail.board_likes}</span>
		</div>

onclick을 추가해서 비동기 방식으로 js를 호출을 해주었다.

toggleLikes 함수에 매개변수로는 likes 테이블에 필요한 값. 

아까 복합키로 지정해둔 값들을 파라미터 값으로 전달해주었다. 아래 두 값.

${requestScope.boardDetail.board_id},'${sessionScope.loginUser.useremail}'

useremail은 string 타입이므로 '' 싱글쿼테이션으로 묶어서 전달해주어야한다.

Board.js

function toggleLikes(board_id, useremail) {
	let url = "/board/toggle";

	axios.post(url, {
		useremail: useremail,
		board_id: parseInt(board_id)
	}, { headers: { 'Content-Type': 'application/json' }  }

	).then(response => {
		let likeCountElement = document.getElementById('likeCount');
		let currentLikeCount = parseInt(likeCountElement.textContent);

		if (response.status === 200) {
			likeCountElement.textContent = currentLikeCount + 1; // 좋아요 추가
		} else if (response.status === 204) {
			likeCountElement.textContent = currentLikeCount - 1; // 좋아요 삭제
		}


	}).catch(err => {
		if (err.response && err.response.status === 502) {
			alert("[삭제 오류]" + err.response.data);
			alert("[시스템 오류]" + err.message);
		}
	});

}

js파일에서는 axios로 값을 넘겨주는 방식을 이용하였다.

 

동영상 시연