Spring

Spring MVC 게시판 만들기 2편: 컨트롤러와 요청 흐름 완벽 이해


💡 이 글에서 배울 것
Spring MVC에서 사용자의 요청이 어떻게 Controller를 거쳐 처리되는지, 그 전체 흐름을 파헤쳐봅니다.


🎯 한눈에 보기

주제핵심 내용
DispatcherServlet모든 요청의 시작점, 안내 데스크 역할
@Controller웹 요청을 처리하는 클래스 표시
@GetMapping/@PostMappingHTTP 메서드별 요청 처리

📌 이번 편에서 배울 것

이번 편에서는 사용자의 요청이 어떻게 처리되는지 알아볼 거예요:

  • DispatcherServlet이 뭘까?
  • @Controller@RequestMapping의 역할
  • 게시글 목록, 등록, 조회, 수정, 삭제가 어떻게 처리되는지

🚪 모든 요청의 시작점: DispatcherServlet

Spring MVC에서 모든 웹 요청DispatcherServlet이라는 특별한 친구를 거쳐갑니다. 마치 회사의 안내 데스크처럼, 어떤 부서(Controller)로 가야 할지 안내해주는 역할이에요!

DispatcherServlet 개념

flowchart LR
    User[👤 사용자] --> |HTTP 요청| DS[DispatcherServlet]
    DS --> |어디로 갈까?| HM[Handler Mapping]
    HM --> |BoardController로!| DS
    DS --> |요청 전달| BC[BoardController]
    BC --> |결과 반환| DS
    DS --> |화면은?| VR[View Resolver]
    VR --> |list.jsp로!| DS
    DS --> |최종 결과| User

web.xml 설정 코드:

<!-- 모든 요청을 DispatcherServlet이 받도록 설정 -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>  <!-- 모든 경로 처리 -->
</servlet-mapping>

🎯 BoardController 한눈에 보기

BoardController.java는 게시판 관련 모든 요청을 담당합니다.

@Controller              // "나는 웹 요청을 처리하는 클래스야!"
@Log4j2                  // 로그 출력용
@RequestMapping("/board") // /board로 시작하는 모든 요청 담당
@RequiredArgsConstructor // 생성자 자동 생성 (DI용)
public class BoardController {

    private final BoardService boardService;  // 서비스 주입
    
    // 여기에 각종 요청 처리 메서드들...
}

📋 게시글 목록 보기 (GET /board/list)

가장 많이 사용하는 기능! 게시글 목록을 가져오는 과정을 보겠습니다.

sequenceDiagram
    participant 📱 as 브라우저
    participant 🎮 as BoardController
    participant 🔧 as BoardService
    participant 📊 as Model
    participant 📄 as list.jsp

    📱->>🎮: GET /board/list?page=1&size=10
    🎮->>🔧: getList(1, 10, null, null)
    🔧-->>🎮: BoardListPaginDTO
    🎮->>📊: model.addAttribute("dto", list)
    🎮-->>📄: return "board/list"
    📄-->>📱: HTML 화면

실제 코드:

@GetMapping("/list")
public void list(
        @RequestParam(name="page", defaultValue = "1") int page,
        @RequestParam(name="size", defaultValue = "10") int size,
        @RequestParam(name="types", required = false) String types,
        @RequestParam(name="keyword", required = false) String keyword,
        Model model) {
    
    log.info("========== board list 호출 ==========");
    
    // 서비스에서 게시글 목록 + 페이징 정보 가져오기
    BoardListPaginDTO list = boardService.getList(page, size, types, keyword);
    
    // JSP에서 사용할 수 있도록 Model에 담기
    model.addAttribute("dto", list);
    
    // 메서드 반환타입이 void면 → 경로 그대로 JSP 찾음
    // /board/list → /WEB-INF/views/board/list.jsp
}

💡 핵심 포인트:

  • @RequestParam: URL의 파라미터를 변수로 받음 (?page=1 → page 변수에 1)
  • defaultValue: 값이 없으면 기본값 사용
  • Model: JSP에 데이터를 전달하는 “가방” 역할

✍️ 게시글 등록 (GET + POST /board/register)

등록은 두 단계로 이루어집니다:

  1. GET: 빈 입력 폼 보여주기
  2. POST: 사용자가 작성한 데이터 저장하기
flowchart TB
    subgraph "1️⃣ 등록 폼 요청"
        A[사용자] --> |GET /board/register| B[Controller]
        B --> |빈 폼 화면| C[register.jsp]
    end
    
    subgraph "2️⃣ 등록 처리"
        D[사용자가 폼 작성] --> |POST /board/register| E[Controller]
        E --> |데이터 저장| F[Service]
        F --> |성공| G[redirect:/board/list]
    end

GET - 빈 폼 보여주기:

@GetMapping("/register")
public void register() {
    log.info("board register");
    // void 반환 → /board/register → register.jsp
}

POST - 데이터 저장하기:

@PostMapping("/register")
public String registerPost(BoardDTO dto, RedirectAttributes rttr) {
    log.info("board register post");
    
    // 게시글 등록하고 등록된 번호 받기
    Long boardNo = boardService.register(dto);
    
    // 등록 완료 메시지 (1회용 데이터)
    rttr.addFlashAttribute("result", boardNo);
    
    // 목록 페이지로 이동
    return "redirect:/board/list";
}

💡 RedirectAttributes란?

  • redirect 할 때 데이터를 전달하는 방법
  • addFlashAttribute: 1회용 데이터 (새로고침하면 사라짐)
  • URL에 노출되지 않아 보안상 안전!

👀 게시글 상세 보기 (GET /board/read/{boardNo})

게시글 번호를 URL 경로에 넣어서 요청합니다.

/board/read/42  ← 42번 게시글 보기

코드:

@GetMapping("/read/{boardNo}")  // {boardNo}는 경로 변수
public String read(
        @PathVariable("boardNo") Long boardNo,  // URL에서 boardNo 추출
        @RequestParam(name="page", defaultValue = "1") int page,
        @RequestParam(name="size", defaultValue = "10") int size,
        @RequestParam(name="types", required = false) String types,
        @RequestParam(name="keyword", required = false) String keyword,
        Model model) {
    
    // 게시글 1건 조회
    BoardDTO dto = boardService.read(boardNo);
    
    // JSP에 전달
    model.addAttribute("board", dto);
    model.addAttribute("page", page);   // 목록으로 돌아갈 때 사용
    model.addAttribute("size", size);
    model.addAttribute("types", types);
    model.addAttribute("keyword", keyword);
    
    return "/board/read";
}

💡 @PathVariable vs @RequestParam:

  • @PathVariable: URL 경로의 일부 /read/{boardNo}/read/42
  • @RequestParam: URL의 쿼리 파라미터 ?page=1

✏️ 게시글 수정 (GET + POST /board/modify)

수정도 등록과 마찬가지로 두 단계!

flowchart LR
    A[목록] --> |수정 클릭| B[GET /board/modify/42]
    B --> |기존 데이터로 폼 표시| C[modify.jsp]
    C --> |수정 버튼 클릭| D[POST /board/modify]
    D --> |저장 완료| E[redirect:/board/read/42]

GET - 수정 폼 보여주기:

@GetMapping("/modify/{boardNo}")
public String modifyGet(@PathVariable("boardNo") Long boardNo, Model model) {
    BoardDTO dto = boardService.read(boardNo);  // 기존 데이터 불러오기
    model.addAttribute("board", dto);
    return "board/modify";
}

POST - 수정 처리:

@PostMapping("/modify")
public String modifyPost(BoardDTO dto) {
    boardService.modify(dto);
    return "redirect:/board/read/" + dto.getBoardNo();  // 상세보기로 이동
}

🗑️ 게시글 삭제 (POST /board/remove)

삭제는 POST 요청으로만 처리합니다. (보안상 GET 삭제는 위험!)

@PostMapping("/remove")
public String remove(
        @RequestParam("boardNo") Long boardNo,
        RedirectAttributes rttr) {
    
    boardService.remove(boardNo);
    
    rttr.addFlashAttribute("result", boardNo);  // 삭제 알림용
    
    return "redirect:/board/list";
}

🔀 요청 처리 방식 비교

기능HTTP 메서드URL반환 방식
목록GET/board/listvoid (경로=JSP)
등록폼GET/board/registervoid
등록처리POST/board/registerredirect
상세보기GET/board/read/{boardNo}String (JSP경로)
수정폼GET/board/modify/{boardNo}String
수정처리POST/board/modifyredirect
삭제POST/board/removeredirect

🎨 View Resolver의 역할

Controller가 "board/list"를 반환하면, 어떻게 list.jsp 파일을 찾을까요?

servlet-context.xml 설정:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>
Controller 반환값: "board/list"

prefix + 반환값 + suffix

"/WEB-INF/views/" + "board/list" + ".jsp"

결과: /WEB-INF/views/board/list.jsp ✓

📝 정리

어노테이션역할
@Controller웹 요청 처리 클래스
@RequestMappingURL 경로 매핑 (클래스/메서드)
@GetMappingGET 요청 처리
@PostMappingPOST 요청 처리
@RequestParamURL 파라미터 받기
@PathVariableURL 경로 변수 받기

🚀 다음 편 예고

**3편: 서비스 계층과 비즈니스 로직 구현하기**에서는:

  • @Service 어노테이션의 의미
  • 페이징 계산 로직 분석
  • 검색 조건 처리 방법

을 알아볼 예정입니다!


📚 시리즈 목차