[Java+SpringBoot+JPA] 테이블 조인을 활용한 댓글 기능+좋아요/싫어요(2)

2024. 1. 11. 22:52Pratice/CRUD

반응형

 

 

[Java+SpringBoot+JPA] 테이블 조인을 활용한 댓글 기능(1)

🟢 Constant 훈련기관 enum 열거형으로 설정 public enum StudyRole { A("우리인재개발원"), B("더조은아카데미"), C("그린컴퓨터"), D("직업전문학원"); private String description; StudyRole(String description) { this.descripti

dalhyehye.tistory.com

 


 

 

 

🟢 CommentEntity

  ▪️ FetchType : 연관 엔티티를 어떻게 가져올 것인지를 지정
  ▪️ FetchType.LAZY : 연관 엔티티가 필요할 때 로딩

                                  (즉, 처음에는 연관 엔티티를 가져오지 않고, 실제로 엔티티에 접근할 때 가져옴)

@Entity
@Getter @Setter @ToString @Builder
@AllArgsConstructor @NoArgsConstructor
@Table(name = "comment")
@SequenceGenerator(
    name = "comment_SEQ",
    sequenceName = "comment_SEQ",
    initialValue = 1,
    allocationSize = 1)
public class CommentEntity extends BaseEntity{

  @Id
  @GeneratedValue (strategy = GenerationType.SEQUENCE, generator = "comment_SEQ")
  @Column(name = "commentId")
  private Integer commentId;          //댓글번호

  @Lob
  @Column(name = "comment", nullable = false, length = 500)
  private String comment;             //댓글내용

  @ColumnDefault("0")
  @Column(name = "goodcnt")
  private  int goodcnt;               //좋아요

  @ColumnDefault("0")
  @Column(name = "badcnt")
  private  int badcnt;                //싫어요

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "sid")
  private StudyEntity studyEntity;    //게시글 조인 (수강과목)

}

 

 

🟢 CommentDTO

외래키 변수로 선언

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CommentDTO extends BaseEntity {

  private Integer commentId;          //번호

  @NotEmpty (message = "내용은 필수 입력입니다.")
  private String comment;                //댓글내용

  private  int goodcnt;               //좋아요

  private  int badcnt;                //싫어요

  private LocalDate credate;          //등록날짜

  private LocalDateTime modidate;     //수정날짜

  private int sid;                    //게시글 번호(외래키)

}

 

 

🟢 CommentRepository

 ▪️  부모테이블에 해당하는 레코드 조회

 ▪️  sid에 해당하는 댓글을 역순 정렬 (commentid 기준)

@Repository
public interface CommentRepositroty extends JpaRepository <CommentEntity, Integer> {

  //부모테이블에 해당하는 레코드 조회
  @Query("SELECT u FROM CommentEntity u WHERE u.studyEntity.sid=:sid")
  List<CommentEntity> findByStudyId (Integer sid);

  //댓글 정렬 기준
  @Query("SELECT u FROM CommentEntity u WHERE u.studyEntity.sid = :sid ORDER BY u.commentId DESC")
  List<CommentEntity> findByStudyIdOrderByCommentIdDesc(Integer sid);

}

 

 ▪️ 좋아요, 싫어요 기능

  //좋아요
  @Modifying
  @Query("UPDATE CommentEntity u SET u.goodcnt=u.goodcnt+1 WHERE u.commentId =:commentId")
  void goodcnt (@Param("commentId") Integer commentId);

  //싫어요
  @Modifying
  @Query("UPDATE CommentEntity u SET u.badcnt=u.badcnt+1 WHERE u.commentId =:commentId")
  void badcnt (@Param("commentId") Integer commentId);

 

🟢 CommentService

▪️ 댓글 등록/목록 : 부모테이블의 레코드 번호 조회

▪️ 댓글 좋아요/싫어요/삭제 : 댓글 번호 조회

▪️ 댓글 등록 번호 기준 역순 정렬

@Service
@Transactional
@RequiredArgsConstructor
public class CommentService {

  private final StudyRepository studyRepository;
  private final CommentRepositroty commentRepositroty;
  private final ModelMapper modelMapper = new ModelMapper();

  //댓글 등록
  //부모테이블의 레코드 번호 필요 (Intger sid)
  public CommentEntity insert(Integer sid, CommentDTO commentDTO) throws Exception {
    
    //부모테이블에서 해당 레코드 조회
    Optional<StudyEntity> data = studyRepository.findById(sid);
    StudyEntity result = data.orElseThrow();

    //댓글 등록
    //DTO로 받아서 Entity로 변환 후, 전달
    CommentEntity commentEntity = modelMapper.map(commentDTO, CommentEntity.class);
    
    //부모 레코드 정보 추가
    commentEntity.setStudyEntity(result);

    //댓글 저장
    commentRepositroty.save(commentEntity);

    return commentEntity;

  }



  //댓글 목록
  public List<CommentDTO> list(Integer sid) throws Exception {
    List<CommentEntity> commentEntityList = commentRepositroty.findByStudyIdOrderByCommentIdDesc(sid);
    List<CommentDTO> commentDTOS = commentEntityList.stream()
        .map(commentEntity -> modelMapper.map(commentEntity, CommentDTO.class))
        .collect(Collectors.toList());
    return commentDTOS;
  }




  //좋아요
  public void goodCnt(Integer commentId) throws Exception {
    commentRepositroty.goodcnt(commentId);
  }

  //싫어요
  public void badCnt(Integer commentId) throws Exception {
    commentRepositroty.badcnt(commentId);
  }

  
  
  //삭제
  public void delete(Integer commentId) throws Exception {
    commentRepositroty.deleteById(commentId);
  }

}

 

 

🟢 CommentController

▪️ StudyController 상세페이지조회 메서드에 댓글 관련 내용 추가

▪️ 댓글등록 할 때, 부모테이블의 Id 필요 / RedirectAttributes로 리다이렉트 해주기

▪️ 댓글 좋아요/싫어요/삭제 할 때는 댓글번호(commentId)도 필요

더보기
  //상세 폼
  @GetMapping("/studydetail")
  public String detailForm(int sid, Model model) throws Exception {

    //게시글 불러 오기
    StudyDTO studyDTO = studyService.findOne(sid);

    //댓글 불러오기
    List<CommentDTO> commentDTOS = commentService.list(sid);
    
    //전달
    model.addAttribute("studyDTO", studyDTO);
    model.addAttribute("commentDTOS", commentDTOS);
    
    return "/study/detail";
  }
@Controller
@RequiredArgsConstructor
public class CommentController {
  
  private final CommentService commentService;

  //댓글등록
  //수강과목의 id필요
  @PostMapping("/commentinsert")
  public String insertForm(int sid, CommentDTO commentDTO, RedirectAttributes redirectAttributes, Model model) throws Exception {

    commentService.insert(sid, commentDTO);

    //studydetail 엔드포인트로 id 매개변수를 사용하여 리다이렉트
    redirectAttributes.addAttribute("sid", sid);

    return "redirect:/studydetail";
  }



  //좋아요
  @GetMapping("/commentgoodcnt")
  public String goodcntProc(int sid, Integer commentId, RedirectAttributes redirectAttributes) throws Exception {
    commentService.goodCnt(commentId);
    redirectAttributes.addAttribute("sid", sid);
    return "redirect:/studydetail";
  }



  //싫어요
  @GetMapping("/commentbadcnt")
  public String badcnt(int sid, Integer commentId, RedirectAttributes redirectAttributes) throws Exception {
    commentService.badCnt(commentId);
    redirectAttributes.addAttribute("sid", sid);
    return "redirect:/studydetail";
  }



  //삭제 폼
  @GetMapping("/commentdelete")
  public String deleteForm(int sid, Integer commentId, RedirectAttributes redirectAttributes) throws Exception {
    commentService.delete(commentId);
    redirectAttributes.addAttribute("sid", sid);
    return "redirect:/studydetail";
  }

}

 

 


 

🟢 detail.html

▪️ 댓글 등록을 위해서는 hidden으로 부모테이블 레코드 조회

▪️ 좋아요/싫어요 수 0으로 초기화

▪️ 좋아요/싫어요/삭제를 위해서는 댓글, 부모레코드  id 필요

<!-- 댓글 영역 시작 -->
      <div class="card mt-3 mb-3">
        <div class="card-header">댓글</div>
        <div class="card-body">
          <div class="card-body">
            <form th:action="@{/commentinsert}" method="post">
              <input type="hidden" name="sid" th:value="${studyDTO.sid}">
              <div class="mb-1 mt-1">
                <textarea class="form-control" rows="3" id="comment" name="comment"></textarea>
              </div>
              <input type="hidden" name="goodcnt" value="0">
              <input type="hidden" name="badcnt" value="0">
              <button type="submit" class="btn btn-primary mt-1">댓글 등록</button>
            </form>
          </div>
        </div>

        <div class="card-footer">
          <div class="card mt-2 mb-2" th:each="data:${commentDTOS}">
            <div class="card-header">
              작성일 : <span th:text="${#temporals.format(data.credate, 'yyyy-MM-dd')}"></span><br>
              <a th:href="@{/commentgoodcnt(sid=${studyDTO.sid}, commentId=${data.commentId})}">좋아요</a> : <span th:text="${data.goodcnt}"></span>
              <a th:href="@{/commentbadcnt(sid=${studyDTO.sid}, commentId=${data.commentId})}">싫어요</a> : <span th:text="${data.badcnt}"></span>
            </div>
            <div class="card-body"><span th:text="${data.comment}"></span></div>
            <div class="card-footer">
              <button type="button" class="btn btn-primary" >수정</button>
              <button type="button" class="btn btn-danger" th:onclick="|location.href='@{/commentdelete(sid=${studyDTO.sid}, commentId=${data.commentId})}'|">삭제</button>
            </div>
          </div>
        </div>
      </div>
      <!-- 댓글 영역 끝 -->

 

반응형