▣ PART · MVC

Controller 해부

@RequestMapping · 파라미터 · Model

학습 목표

  • Controller 메서드 한 개를 라인별로 짚을 수 있다
  • URL 매핑·파라미터 받기·Model 담기 3 가지를 안다
  • @RequestParam · @PathVariable · @ModelAttribute 의 차이를 안다
  • 반환 타입(String / void / 객체) 별 동작을 안다

⚠️ 컨트롤러 한 개의 모습

처음 보는 코드

@Controller
@RequestMapping("/board")
public class BoardController {

    @Autowired
    private BoardService service;

    @GetMapping("/view/{id}")
    public String view(@PathVariable int id,
                       @RequestParam(defaultValue="false") boolean comment,
                       Model model,
                       HttpSession session) {
        model.addAttribute("board", service.findById(id));
        return "board/view";
    }
}

단순해 보이지만 어노테이션·매개변수·반환값이 모두 의미가 다름. 한 줄씩 분해해서 봐야 합니다.

🛠️ Controller 의 3 가지 일

URL 매핑 + 파라미터 받기 + Model 담기

이 3 가지가 컨트롤러 메서드의 본질. Service 호출은 그 사이에 끼우는 「위임」 한 줄.

① 어떤 URL 을 처리할까 → @GetMapping, @PostMapping ② 어떤 데이터를 받을까 → @RequestParam, @PathVariable, ... ③ 어떤 화면을 보여줄까 → return "viewName" + Model 에 데이터

① 클래스 어노테이션 — @Controller


@Controller
public class BoardController { ... }
  • @Controller = 「이 클래스는 Bean 이고 + 웹 요청을 처리한다」
  • Spring 컨테이너가 자동 등록
  • @Component 의 한 종류 (의미적 표시)
  • @RestController 와의 차이는 Part 6 에서

② @RequestMapping — URL 매핑


@Controller
@RequestMapping("/board")        // 클래스 레벨 prefix
public class BoardController {

    @GetMapping("/list")          // GET /board/list
    public String list() { ... }

    @GetMapping("/view/{id}")     // GET /board/view/3
    public String view(@PathVariable int id) { ... }

    @PostMapping("/write")        // POST /board/write
    public String write(Board b) { ... }
}

👉 클래스 + 메서드 어노테이션이 합쳐져 최종 URL.

② HTTP 메서드별 어노테이션

어노테이션HTTP 메서드전형적 용도
@GetMappingGET조회 — 목록·상세 보기
@PostMappingPOST제출·생성 — 폼 처리
@PutMappingPUT수정 (REST API 에서)
@DeleteMappingDELETE삭제 (REST API 에서)
@RequestMapping전부위 4 가지의 일반형

👉 옛 코드는 @RequestMapping(method=GET) 형식. 새 코드는 짧은 @GetMapping 권장.

③ 파라미터 받기 — @RequestParam


@GetMapping("/search")
public String search(
    @RequestParam("q") String query,                    // 필수
    @RequestParam(defaultValue = "1") int page,         // 기본값
    @RequestParam(required = false) String category,    // 선택
    Model model
) {
    // URL: /search?q=spring&page=2&category=java
    // → query="spring", page=2, category="java"
}
  • URL 의 ?key=value 부분(쿼리스트링)을 받음
  • required=false 또는 defaultValue 로 선택 처리
  • 변수명이 같으면 ("q") 생략 가능: @RequestParam String q

③ @PathVariable — URL 경로의 일부


@GetMapping("/board/view/{id}")
public String view(@PathVariable int id) {
    // URL: /board/view/3
    // → id = 3
}

@GetMapping("/board/{boardId}/comment/{commentId}")
public String comment(
    @PathVariable int boardId,
    @PathVariable int commentId
) {
    // URL: /board/3/comment/7
    // → boardId=3, commentId=7
}

👉 REST API 스타일에서 자주 사용. 「리소스를 URL 로 표현」.

③ 객체로 받기 — 자동 바인딩


public class Board {                  // VO
    private String title;
    private String content;
    // getter/setter
}

@PostMapping("/board/write")
public String write(Board board) {     // ⭐ 자동 바인딩
    service.insert(board);
    return "redirect:/board/list";
}

<!-- write.jsp -->
<form action="/board/write" method="post">
    <input name="title" />        <!-- ↔ board.title -->
    <textarea name="content"></textarea>
    <button>저장</button>
</form>

👉 form input 의 name 속성과 VO 필드명이 정확히 일치하면 Spring 이 자동 채워줌.

③ 파라미터 받는 4 가지 정리

방식URL/Form 형태코드
@RequestParam?id=3@RequestParam int id
@PathVariableURL /board/3@PathVariable int id
VO 자동form: name="title", "content"Board board
@CookieValueCookie: theme=dark@CookieValue String theme

④ 응답 데이터 — Model


@GetMapping("/board/view")
public String view(@RequestParam int id, Model model) {
    Board b = service.findById(id);

    model.addAttribute("board", b);          // ${board}
    model.addAttribute("comments",
                        commentService.list(id));   // ${comments}
    model.addAttribute("isOwner",
                        b.getWriterId() == 5);     // ${isOwner}

    return "board/view";
}

<!-- view.jsp -->
<h1>${board.title}</h1>
<c:if test="${isOwner}"><a>수정</a></c:if>
<c:forEach var="c" items="${comments}">
    <p>${c.content}</p>
</c:forEach>

⑤ 반환 타입 — String 의 의미


return "board/view";
// → ViewResolver 가 /WEB-INF/views/board/view.jsp 로 변환

return "redirect:/board/list";
// → 브라우저에 302 리다이렉트 응답
//   브라우저가 /board/list 로 다시 요청

return "forward:/board/list";
// → 서버 내부에서 다른 컨트롤러로 위임
//   브라우저는 변화 모름

👉 redirect:forward: 가 가장 자주 보는 두 접두사.

⑤ Redirect 의 활용 — POST 후 GET 패턴


@PostMapping("/board/write")
public String write(Board b) {
    service.insert(b);
    return "redirect:/board/list";
    // ⭐ "redirect:" 의 이유?
    // 1) 새로고침 시 글이 두 번 등록되는 것 방지
    // 2) 브라우저 주소창의 URL 이 깔끔하게 바뀜
    // 3) "Post-Redirect-Get(PRG) 패턴"
}

POST 후 곧바로 JSP 를 반환하면 → 새로고침 시 「양식을 다시 제출하시겠습니까?」 경고. redirect: 가 이 문제를 풀어줌.

⑥ 추가 매개변수 — 자주 보는 것들


public String list(
    Model model,                           // 데이터 운반
    HttpSession session,                   // 세션
    HttpServletRequest request,            // 원본 요청
    HttpServletResponse response,          // 원본 응답
    RedirectAttributes ra,                 // 리다이렉트 시 데이터
    @CookieValue String theme,             // 쿠키
    @RequestHeader("User-Agent") String ua // HTTP 헤더
) { ... }

👉 Spring 이 매개변수 타입을 보고 자동으로 채워줍니다. 순서·개수 자유.

⑦ 흐름 한 줄로 — view 메서드 사례


@GetMapping("/board/view/{id}")    // ① URL 매핑
public String view(
    @PathVariable int id,           // ② URL 의 {id} 자동 받기
    Model model                     // ② 데이터 그릇
) {
    Board b = service.findById(id); // ③ Service 호출
    model.addAttribute("board", b); // ③ 그릇에 담기
    return "board/view";            // ④ View 이름 반환
}
브라우저 GET /board/view/3 ↓ ① 매핑 발견 view(3, model) 메서드 호출 ↓ ② id=3, 빈 model service.findById(3) ↓ ③ Board 객체 받기 model.addAttribute("board", ...) ↓ ④ board/view → JSP 경로 ViewResolver → /WEB-INF/views/board/view.jsp → ${board.title} 등이 채워짐 → 응답 HTML

실수 모음 — 자주 만나는 문제

증상원인
404 Not FoundURL 경로 / @GetMapping 오타
매개변수 nullform input name ↔ VO 필드명 불일치
"Required parameter not present"@RequestParam 필수인데 안 보냄
JSP 에 ${} 그대로 표시EL 해석 안 됨 (옛 JSP 호환 옵션)
새로고침 시 폼 재제출 경고POST 후 redirect: 안 씀
"Could not resolve view"return 의 ViewName 오타

좋은 Controller 의 5 가지 특징

  • 얇다 — 비즈니스 로직 없이 위임만
  • 한 메서드 = 한 URL — 책임 명확
  • 예외 처리는 별도로 — try-catch 남발 X (@ExceptionHandler 활용)
  • Service 만 호출 — DAO 직접 호출 X
  • 적절한 반환 타입 — JSP 면 String, JSON 이면 객체

실험 — 직접 깨뜨려보기

실험예상 결과
@RequestParam 필수, 폼에서 안 보내기400 Bad Request
VO 필드명 ↔ form name 다르게매개변수 null (에러 X)
return "이상한값";"Could not resolve view"
@PathVariable 의 이름 다르게"Missing URI template variable"

👉 의도적으로 깨뜨려보면 「어떤 오류 메시지 = 어떤 원인」 매핑이 머리에 박힙니다.

🔄 Before / After

전 차시 끝

Controller 가 무엇인지 정도만 안다. 한 메서드 안의 코드가 무엇을 하는지는 흐릿.

이번 차시 끝

Controller 메서드 한 개를 라인별로 짚는다. URL 매핑·파라미터·Model·반환을 따로따로 본다.

이번 차시의 데이터 흐름

URL
@GetMapping
매개변수 자동 채움
Service 호출
Model 담기
ViewName 반환
컨트롤러 내부 단계가 모두 보임

정리

오늘 들고 가는 것

  • Controller 의 3 가지 일: URL 매핑 + 파라미터 받기 + Model 담기
  • @GetMapping/PostMapping/PutMapping/DeleteMapping
  • @RequestParam · @PathVariable · VO 자동 바인딩
  • return "redirect:" 와 "forward:" 의 차이
  • 좋은 Controller = 얇고 위임만

다음: Service 계층 — 비즈니스 로직과 트랜잭션의 자리.