Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- 시스템 환경 변수 편집
- IntelliJ IDEA
- 메서드 오버로딩
- for문
- 집합관계
- this예약어
- Java
- 형 변환
- JAVA기초
- 인텔리제이 한글 깨짐 해결법
- 메서드
- 연관관계
- OPP개념
- multi-threading
- While
- 반복문
- Java데이터 타입
- java변수
- function
- 컴파일
- 포함관계
- 자바 멀티스레딩
- 상수
- 접근제어지시자
- 생성자
- JAVA객체지향
- break문
- continue문
- 인텔리제이 기초 설정
- Thread
Archives
- Today
- Total
최원종의 개발 블로그
V7-4 댓글 삭제 기능 추가 본문
https://github.com/User20202373/spring_blog
GitHub - User20202373/spring_blog
Contribute to User20202373/spring_blog development by creating an account on GitHub.
github.com

board/ detail.mustache 코드 추가
{{#isOwner}}
<form action="/reply/{{id}}/delete" method="post">
<input type="hidden" name="boardId" value="{{board.id}}" >
<button type="submit" class="btn btn-danger btn-sm">🗑️ 삭제</button>
</form>
{{/isOwner}}
ReplyService 코드 요약
Stream API 기반 리팩토링 : 기존의 명령형 for 루프 스타일을 선언형 Stream API(.map, .toList)로
전환하여 코드 가독성 향상
화면 맞춤 DTO 변환 및 상태 전달 : Entity 리스트를 뷰 레이어에 최적화된 ListDTO로 변환하는 과정에서
sessionUserId를 주입해 본인 글 여부 판별 기반 마련
댓글 삭제(DELETE) 비즈니스 로직 추가 : findById와 orElseThrow 조합으로
대상 댓글의 존재 여부를 선제적으로 검증 후 삭제 프로세스 수행
철저한 인가(Authorization) 방어선 구축 : 댓글 작성자 ID와 로그인한 세션 유저 ID를 비교하여
일치하지 않을 시 Exception403(Forbidden) 예외 차단
ReplyService 코드 리팩토링(댓글삭제 추가)⬇️
더보기
package com.tenco.blog.reply;
import com.tenco.blog._core.errors.Exception403;
import com.tenco.blog._core.errors.Exception404;
import com.tenco.blog.board.Board;
import com.tenco.blog.board.BoardRepository;
import com.tenco.blog.user.User;
import com.tenco.blog.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
// Service 계층에서는 여러 Repository를 조합해서 비즈니스 규칙을 완성한다
// 즉, 서비스 계층이 필요한 이유 중 하나이다.
@Service // IoC
@RequiredArgsConstructor // DI 처리
public class ReplyService {
private final ReplyRepository replyRepository;
private final BoardRepository boardRepository;
private final UserRepository userRepository;
// 댓글 목록 조회
public List<ReplyResponse.ListDTO> 댓글목록조회(Integer boardId, Integer sessionUserId) {
List<Reply> replyList = replyRepository.findByBoardIdWithUser(boardId);
// List<ReplyResponse.ListDTO> listDtoList = new ArrayList<>();
// for(int i =0; i < replyList.size(); i++) {
// Reply tempReply = replyList.get(i);
// // 객체 생성을 위해 필요한 데이터
// ReplyResponse.ListDTO listDTO = new ReplyResponse.ListDTO(tempReply, sessionUserId);
// listDtoList.add(listDTO);
// }
// Stream API 활용
return replyList.stream()
.map(reply -> new ReplyResponse.ListDTO(reply, sessionUserId))
.toList();
}
@Transactional
public Reply 댓글작성(ReplyRequest.SaveDTO saveDTO, Integer id) {
// 게시글 조회
Board boardEntity = boardRepository.findById(saveDTO.getBoardId()).orElseThrow(
() -> new Exception404("해당 게시글을 찾을 수 없습니다"));
// id 로 사용자 조회
User userEntity = userRepository.findById(id).orElseThrow(
() -> new Exception404("사용자를 찾을 수 없습니다")
);
Reply reply = saveDTO.toEntity(userEntity, boardEntity);
replyRepository.save(reply);
return reply;
}
@Transactional
public void 댓글삭제(Integer replyId, Integer sessionUserId) {
Reply replyEntity = replyRepository.findById(replyId).orElseThrow(
() -> new Exception404("해당 댓글을 찾을 수 없습니다"));
// 인가 처리
if(replyEntity.getUser().getId() != sessionUserId) {
throw new Exception403("댓글 삭제 권한이 없습니다");
}
// 댓글 삭제
replyRepository.delete(replyEntity);
}
}
BoardService (기존에 작성된 댓글부터 전체 삭제 기능)
/**
* 게시글 삭제 요청
* @param id (Board PK)
* @param sessionUser
*/
@Transactional
public void 게시글삭제(Integer id, User sessionUser) {
log.info("게시글 삭제 서비스");
Board boardEntity = boardRepository.findById(id).orElseThrow(
() -> new Exception404("게시글을 찾을 수 없습니다")
);
boardEntity.isOwner(sessionUser.getId());
// 기존에 작성된 댓글 부터 전체 삭제
// 게시글 삭제 요청시 해당 게시글에 관련된 댓글 삭제는 어떻게? 만들 수 있음?
replyRepository.deleteByBoardId(boardEntity.getId());
boardRepository.deleteById(id);
log.info("게시글 삭제 완료 - ID : {}", id);
}
ReplyRepository
코드 요약 - @Modifying (반드시 기억)
벌크 삭제(Bulk Delete) 전용 쿼리 구축 : 특정 게시글 ID(boardId)에 종속된 모든 댓글을
한방에 날리는 데이터 정제용 JPQL 설계
@Query 기반 쓰기 연산 선언 : 표준 CRUD를 넘어 직접 커스텀 JPQL을 작성하여
조건 기반 대량 데이터 삭제 환경 마련
@Modifying 어노테이션 필수 적용 : JPA에게 해당 쿼리가 SELECT가 아닌
데이터 변경(INSERT, UPDATE, DELETE) 작업임을 명시
데이터 무결성 방어 : 게시글 삭제 시 외래키(FK) 제약 조건으로 인한 오류를 방지하는
연쇄 삭제(Cascade) 처리의 기반 마련
ReplyRepository 코드(@Modifying 적용한 쿼리문 추가)
package com.tenco.blog.reply;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
// @Repository - 부모의 클래스에 정의 되어 있음()
public interface ReplyRepository extends JpaRepository<Reply, Integer> {
// 1. 등록 및 수정 save(Reply entity)
// 2. 단건 조회 : findById(Integer id)
// 3. 전체 조회 : findAll()
// 4. 삭제 : deleteById(Integer id) - reply
// 5. 데이터 개수: count()
// 6. 존재 여부 확인: existsById(Integer id)
// ... 생략
/**
* 이전 수정,삭제 기능에서는 수정은 더티 체킹으로 처리를 하였고 삭제는 기본적으로
* 제공하는 em.remove() 메서드를 사용해서 처리 했었다. 지금은 직접 JQPL 쿼리를
* 선언해서 DELETE 처리하는 구문이라 다른 상황이다.
* @Query(...) <- JPA 기본적으로 SELECT 쿼리로만 인식을 하기 때문에
* INSERT, UPDATE, DELETE 는 JPA 에게 SELECT 쿼리가 아니야 라고 알려줘야 제대로 동작
* 그 어노테이션이 @Modifying 이다 !! 반드시 기억 !!!!
*/
@Modifying
@Query("DELETE FROM Reply r WHERE r.board.id = :boardId")
void deleteByBoardId(@Param("boardId") Integer boardId);
}
'Spring boot 입문' 카테고리의 다른 글
| V9 게시글 목록 검색 기능 추가 (0) | 2026.05.21 |
|---|---|
| V8 게시글 페이징 처리 (0) | 2026.05.21 |
| V7-3 댓글 조회 기능 추가 (0) | 2026.05.21 |
| V7-2 댓글 작성하기 기능 추가 (0) | 2026.05.20 |
| 스프링부트 V7 댓글 기능 추가 (User 도메인 리팩토링 ) (0) | 2026.05.20 |