Spring

Spring MVC 게시판 만들기 6편: 댓글 기능과 예외 처리 마스터하기


💡 시리즈 완결편!
마지막으로 댓글 기능과 예외 처리를 다루며, 전체 시리즈를 마무리합니다.


🎯 한눈에 보기

주제핵심 내용
ReplyService댓글 CRUD 비즈니스 로직
커스텀 예외HTTP 상태 코드 + 에러 메시지
예외 처리try-catch로 안전한 코드 작성

📌 이번 편에서 배울 것

마지막 편에서는 댓글 기능예외 처리를 알아봅니다:

  • ReplyService의 CRUD 로직
  • 커스텀 예외 클래스 만들기
  • 예외 발생 시 처리 방법

🗂️ 댓글 관련 파일 구조

📁 org/zerock/
├── 📁 dto/
│   ├── ReplyDTO.java          # 댓글 데이터
│   └── ReplyListPaginDTO.java # 댓글 목록 + 페이징
├── 📁 mapper/
│   └── ReplyMapper.java       # 매퍼 인터페이스
├── 📁 service/
│   ├── ReplyService.java      # 비즈니스 로직
│   └── 📁 exeption/
│       └── ReplyExeption.java # 커스텀 예외
└── 📁 resources/mapper/
    └── ReplyMapper.xml        # SQL 쿼리

📦 ReplyDTO (댓글 데이터)

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ReplyDTO {
    private int rno;           // 댓글 번호
    private Long bno;          // 게시글 번호 (어떤 글의 댓글인지)
    private String replyText;  // 댓글 내용
    private String replyer;    // 댓글 작성자
    private LocalDateTime replyDate;   // 작성일
    private LocalDateTime updateDate;  // 수정일
    private boolean delflag;   // 삭제 여부
}

⚠️ 커스텀 예외 클래스

에러 핸들링 개념

댓글 관련 에러를 위한 전용 예외 클래스입니다:

@Getter
public class ReplyExeption extends RuntimeException {
    private int code;     // HTTP 상태 코드
    private String msg;   // 에러 메시지

    public ReplyExeption(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

💡 왜 커스텀 예외?

  • 에러 종류를 명확히 구분 가능
  • HTTP 상태 코드를 함께 전달 가능
  • 일관된 에러 처리 가능

🔧 ReplyService 분석

댓글 추가:

public void add(ReplyDTO replyDTO) {
    try {
        replyMapper.insert(replyDTO);
    } catch (Exception e) {
        throw new ReplyExeption(500, "댓글 추가 실패");
    }
}

댓글 조회:

public ReplyDTO getOne(int rno) {
    try {
        return replyMapper.read(rno);
    } catch (Exception e) {
        throw new ReplyExeption(404, "Not Found");
    }
}

댓글 수정:

public void modify(ReplyDTO replyDTO) {
    try {
        int count = replyMapper.update(replyDTO);
        if (count == 0) {
            throw new ReplyExeption(404, "Not Found");
        }
    } catch (Exception e) {
        throw new ReplyExeption(500, "댓글 수정 실패");
    }
}

댓글 삭제:

public void remove(int rno) {
    try {
        int count = replyMapper.delete(rno);
        if (count == 0) {
            throw new ReplyExeption(404, "Not Found");
        }
    } catch (Exception e) {
        throw new ReplyExeption(500, "댓글 삭제 실패");
    }
}

📋 댓글 목록 조회 (페이징)

public ReplyListPaginDTO listOfBoard(Long bno, int page, int size) {
    try {
        int skip = (page - 1) * size;
        
        // 댓글 목록 조회
        List<ReplyDTO> replyDTOList = replyMapper.listOfBoard(bno, skip, size);
        
        // 전체 개수 조회
        int count = replyMapper.countOfBoard(bno);

        return new ReplyListPaginDTO(replyDTOList, count, page, size);
        
    } catch (Exception e) {
        throw new ReplyExeption(500, "댓글 목록 조회 실패");
    }
}
flowchart LR
    A["bno=42, page=1, size=5"] --> B["skip = 0"]
    B --> C["listOfBoard(42, 0, 5)"]
    C --> D["countOfBoard(42)"]
    D --> E["ReplyListPaginDTO 반환"]

📄 ReplyMapper.xml

댓글 등록:

<insert id="insert">
    <selectKey order="AFTER" keyProperty="rno" resultType="int">
        SELECT LAST_INSERT_ID()
    </selectKey>
    
    INSERT INTO tbl_reply(bno, replyText, replyer)
    VALUES (#{bno}, #{replyText}, #{replyer})
</insert>

댓글 목록 조회:

<select id="listOfBoard" resultType="org.zerock.dto.ReplyDTO">
    SELECT * FROM tbl_reply 
    WHERE bno = #{bno} AND rno > 0
    ORDER BY rno ASC
    LIMIT #{limit} OFFSET #{skip}
</select>

댓글 수정:

<update id="update">
    UPDATE tbl_reply 
    SET replyText = #{replyText}, updatedate = now() 
    WHERE rno = #{rno}
</update>

댓글 삭제:

<delete id="delete">
    DELETE FROM tbl_reply WHERE rno = #{rno}
</delete>

💡 게시글은 소프트 삭제(delflag=true), 댓글은 실제 삭제(DELETE)를 사용합니다!


🔄 예외 처리 흐름

flowchart TB
    Request["댓글 조회 요청"] --> Service["ReplyService.getOne()"]
    Service --> Try{"try 블록"}
    Try --> |성공| Return["ReplyDTO 반환"]
    Try --> |실패| Catch["catch 블록"]
    Catch --> Throw["throw ReplyExeption(404, 'Not Found')"]
    Throw --> Handler["예외 처리기"]
    Handler --> Response["에러 응답 반환"]

📝 시리즈 전체 요약

6편에 걸쳐 Spring MVC 게시판 프로젝트의 모든 부분을 살펴봤습니다!

flowchart TB
    subgraph "1편: 전체 구조"
        A1[기술 스택]
        A2[폴더 구조]
        A3[3-Tier 아키텍처]
    end
    
    subgraph "2편: Controller"
        B1["@Controller"]
        B2["@GetMapping/@PostMapping"]
        B3[View Resolver]
    end
    
    subgraph "3편: Service"
        C1["@Service"]
        C2[DI]
        C3[페이징 계산]
    end
    
    subgraph "4편: MyBatis"
        D1[Mapper]
        D2[XML]
        D3[동적 SQL]
    end
    
    subgraph "5편: JSP"
        E1[JSTL]
        E2[Bootstrap]
        E3[JavaScript]
    end
    
    subgraph "6편: 댓글"
        F1[Reply CRUD]
        F2[커스텀 예외]
        F3[에러 처리]
    end
핵심 내용
1편전체 구조와 기술 스택
2편Controller가 요청을 받고 처리하는 과정
3편Service에서 비즈니스 로직 처리
4편MyBatis로 DB 연동
5편JSP에서 페이징과 검색 UI 구현
6편댓글 기능과 예외 처리

🎯 핵심 키워드 정리

키워드설명
Spring MVCModel-View-Controller 패턴 웹 프레임워크
MyBatisSQL과 Java를 연결하는 매퍼 프레임워크
HikariCP빠른 데이터베이스 커넥션 풀
DI의존성 주입 - Spring이 객체를 자동 연결
DTO계층 간 데이터 전달 객체
JSTLJSP Standard Tag Library
동적 SQL조건에 따라 변하는 SQL
소프트 삭제실제 삭제 대신 플래그로 표시

🏁 마무리

이 시리즈를 통해 Spring MVC 게시판의 전체 흐름을 이해하셨길 바랍니다!

사용자 요청 

DispatcherServlet (문지기)

Controller (요청 처리)

Service (비즈니스 로직)

Mapper + MyBatis (DB 연동)

JSP (화면 렌더링)

사용자에게 응답! ✨

📚 시리즈 전체 목차


💬 질문이나 피드백이 있으시면 댓글로 남겨주세요!
이 시리즈가 도움이 되었다면 공유해주시면 감사하겠습니다 🙏