삶 가운데 남긴 기록 AACII.TISTORY.COM
Spring MVC에서 Model과 ModelAndView 그리고 CRUD 본문
Model과 ModelAndView의 차이점
| 특징 | Model | ModelAndView |
| 역할 | 뷰에 전달할 데이터(속성)만 담는 역할 (인터페이스) | 데이터(Model)와 뷰 이름(View)을 모두 담는 컨테이너 (클래스) |
| 뷰 지정 | 컨트롤러 메서드의 반환 값(String)으로 뷰 이름을 지정 | 객체 내부의 setViewName() 메서드로 뷰 이름을 지정 |
| 주요 사용처 | 최근 Spring 개발 트렌드에서 주로 사용 (String 리턴 타입과 함께) |
Spring 초기에 자주 사용되었으나, 최근에는 사용 빈도가 감소 |
| 반환 타입 | 컨트롤러 메서드의 파라미터로 받으며, 메서드는 보통 뷰 이름(String)을 반환 | 컨트롤러 메서드의 반환 타입이 됨 |
Model
Model 객체는 컨트롤러 메서드의 파라미터로 주입받아 사용하며, 데이터를 추가합니다. 메서드는 뷰의 논리적 이름(String)을 반환합니다.
컨트롤러 예시:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
@GetMapping("/model-example")
public String useModel(Model model) {
// Model에 'message'라는 이름으로 데이터 추가
model.addAttribute("message", "Model 객체를 사용한 데이터입니다.");
// 뷰의 논리적 이름 반환 (ViewResolver가 'modelView.jsp' 또는 'modelView.html' 등을 찾음)
return "modelView";
}
}
뷰(modelView.jsp) 예시:
<body>
<h1>Model 예제</h1>
<p>받은 메시지: ${message}</p>
</body>
ModelAndView
ModelAndView 객체를 생성하여 데이터를 추가하고 뷰 이름까지 설정한 후, 객체 자체를 반환합니다.
컨트롤러 예시:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
@GetMapping("/mav-example")
public ModelAndView useModelAndView() {
// ModelAndView 객체 생성 및 뷰 이름 설정
ModelAndView mav = new ModelAndView("mavView");
// addObject()를 사용하여 데이터 추가
mav.addObject("greeting", "ModelAndView를 사용한 환영 메시지입니다.");
// ModelAndView 객체 자체를 반환
return mav;
}
}
뷰(mavView.jsp) 예시:
<body>
<h1>ModelAndView 예제</h1>
<p>받은 인사: ${greeting}</p>
</body>
@ModelAttribute
@ModelAttribute 어노테이션은 Spring MVC에서 HTTP 요청 파라미터와 Model 객체를 처리하는데 사용됩니다.
1. 컨트롤러 메서드의 파라미터에서 사용될 때 (데이터 바인딩 및 Model에 추가)
요청 데이터를 객체에 자동으로 매핑하고 이 객체를 뷰에 전달 합니다.
@ModelAttribute가 컨트롤러 메서드의 파라미터에 사용될 때, Spring은 다음 세 가지 작업을 수행합니다.
- 객체 생성: 파라미터 타입에 해당하는 객체(DTO 또는 VO)를 자동으로 생성합니다.
- 데이터 바인딩 (Data Binding): HTTP 요청(예: 폼 데이터, 쿼리 파라미터)에서 넘어온 값들을 자동으로 새로 생성된 객체의 필드에 바인딩(매핑)합니다. 이 과정을 통해 개발자가 일일이 request.getParameter()를 호출할 필요가 없습니다.
- Model에 추가: 바인딩이 완료된 객체를 자동으로 Model 객체에 추가하여 뷰(View)에서 사용할 수 있도록 합니다. 이때 Model 속성의 이름은 기본적으로 해당 클래스의 이름(소문자로 시작)을 따릅니다.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
// Student DTO/VO 클래스가 있다고 가정
public class Student {
private String name;
private int age;
// getter, setter 생략
}
@Controller
public class FormController {
// HTTP 요청 파라미터 (예: name=John&age=20)가 Student 객체에 자동 바인딩됨
@PostMapping("/register")
// @ModelAttribute("studentInfo") Student student
// -> student라는 객체를 Model에 "studentInfo"라는 이름으로 추가
public String register(@ModelAttribute("studentInfo") Student student) {
// student 객체에는 이미 요청 파라미터로 넘어온 name과 age 값이 설정되어 있음
System.out.println("등록된 학생 이름: " + student.getName());
// Model에도 "studentInfo"라는 이름으로 student 객체가 추가되어 뷰로 전달됨
return "registrationResult";
}
}
2. 컨트롤러 메서드의 반환 값 (공통 참조 데이터 추가)
해당 컨트롤러의 모든 요청 처리 전에 실행되어, 공통 데이터를 Model에 미리 설정합니다.
@ModelAttribute가 컨트롤러 클래스 내의 특정 메서드에 사용될 때, 이 메서드가 반환하는 객체를 @RequestMapping 등으로 매핑된 요청 핸들러 메서드가 사용하기 전에 Model에 자동으로 추가하도록 지정합니다.
이는 해당 컨트롤러 내의 여러 뷰에서 공통적으로 필요한 드롭다운 목록이나 사이트 메뉴 등을 Model에 미리 담아두는 데 유용합니다.
@Controller
public class CommonController {
// 이 컨트롤러의 어떤 @RequestMapping 메서드가 호출되든,
// 이 메서드가 먼저 실행되고 반환 값이 Model에 "commonData"라는 이름으로 추가됨
@ModelAttribute("commonData")
public List<String> getCommonData() {
return Arrays.asList("메뉴1", "메뉴2", "메뉴3");
}
@GetMapping("/home")
public String home() {
// home.jsp에서 ${commonData}를 사용할 수 있음
return "home";
}
@GetMapping("/about")
public String about() {
// about.jsp에서도 ${commonData}를 사용할 수 있음
return "about";
}
}
Spring MVC 게시판 CRUD 예제
1. Model(데이터 객체):
// src/main/java/.../model/Board.java
public class Board {
private Long id;
private String title;
private String content;
// 생성자 (Constructor)
public Board() {}
public Board(Long id, String title, String content) {
this.id = id;
this.title = title;
this.content = content;
}
// Getter와 Setter 생략
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
2. Service(비지니스 로직)
// src/main/java/.../service/BoardService.java
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class BoardService {
// 메모리 저장소 (실제로는 DB 연동)
private final List<Board> boards = new ArrayList<>();
private final AtomicLong counter = new AtomicLong(0); // ID 생성을 위한 카운터
// 초기 데이터 설정
public BoardService() {
save(new Board(null, "첫 번째 글", "내용입니다."));
save(new Board(null, "두 번째 글", "더 긴 내용입니다."));
}
// 1. Create: 새 글 저장
public Board save(Board board) {
board.setId(counter.incrementAndGet()); // 새 ID 할당
boards.add(board);
return board;
}
// 2. Read All: 모든 글 목록 조회
public List<Board> findAll() {
return boards;
}
// 2. Read One: 특정 ID의 글 조회
public Board findById(Long id) {
return boards.stream()
.filter(board -> board.getId().equals(id))
.findFirst()
.orElse(null);
}
// 3. Update: 글 수정
public Board update(Long id, Board updatedBoard) {
Board existingBoard = findById(id);
if (existingBoard != null) {
existingBoard.setTitle(updatedBoard.getTitle());
existingBoard.setContent(updatedBoard.getContent());
return existingBoard;
}
return null; // 수정할 글이 없는 경우
}
// 4. Delete: 글 삭제
public boolean delete(Long id) {
return boards.removeIf(board -> board.getId().equals(id));
}
}
3. Controller (요청 제어 및 View 전달)
HTTP 요청을 받아 서비스를 호출하고 결과를 Model에 담아 View 이름으로 반환합니다.
// src/main/java/.../controller/BoardController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/boards")
public class BoardController {
private final BoardService boardService;
@Autowired
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
// **1. Read All (목록 조회)**: GET /boards
@GetMapping
public String listBoards(Model model) {
model.addAttribute("boards", boardService.findAll());
return "boardList"; // boardList.jsp 또는 boardList.html로 이동
}
// **2. Create (등록 폼)**: GET /boards/new
@GetMapping("/new")
public String showCreateForm(Model model) {
model.addAttribute("board", new Board()); // 빈 Board 객체를 모델에 담아 폼에 전달
return "boardForm";
}
// **3. Create (등록 처리)**: POST /boards
@PostMapping
public String createBoard(@ModelAttribute Board board) {
boardService.save(board);
return "redirect:/boards"; // 목록 페이지로 리다이렉트
}
// **4. Read One (상세 조회)**: GET /boards/{id}
@GetMapping("/{id}")
public String viewBoard(@PathVariable Long id, Model model) {
Board board = boardService.findById(id);
if (board == null) {
return "redirect:/boards"; // 글이 없으면 목록으로
}
model.addAttribute("board", board);
return "boardDetail"; // boardDetail.jsp로 이동
}
// **5. Update (수정 폼)**: GET /boards/{id}/edit
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable Long id, Model model) {
Board board = boardService.findById(id);
if (board == null) {
return "redirect:/boards";
}
model.addAttribute("board", board);
return "boardForm"; // 등록 폼과 동일한 뷰 사용
}
// **6. Update (수정 처리)**: POST /boards/{id} (PUT/PATCH는 폼에서 지원하기 어려워 POST 사용)
@PostMapping("/{id}")
public String updateBoard(@PathVariable Long id, @ModelAttribute Board board) {
boardService.update(id, board);
return "redirect:/boards/" + id; // 상세 페이지로 리다이렉트
}
// **7. Delete (삭제 처리)**: POST /boards/{id}/delete
@PostMapping("/{id}/delete")
public String deleteBoard(@PathVariable Long id) {
boardService.delete(id);
return "redirect:/boards"; // 목록 페이지로 리다이렉트
}
}
4. View(jsp / Thymeleaf)
여기서는 jsp를 예시로 듭니다.
boardList.jsp 목록 조회
<h2>게시글 목록</h2>
<p><a href="/boards/new">새 글 등록</a></p>
<table border="1">
<tr>
<th>ID</th>
<th>제목</th>
<th>관리</th>
</tr>
<c:forEach var="board" items="${boards}">
<tr>
<td>${board.id}</td>
<td><a href="/boards/${board.id}">${board.title}</a></td>
<td>
<a href="/boards/${board.id}/edit">수정</a>
</td>
</tr>
</c:forEach>
</table>
boardForm.jsp(등록 및 수정 폼)
<h2>${board.id == null ? '새 글 등록' : '글 수정'}</h2>
<form action="/boards${board.id != null ? '/' : ''}${board.id != null ? board.id : ''}" method="POST">
<p>
제목: <input type="text" name="title" value="${board.title}" required/>
</p>
<p>
내용: <textarea name="content" required>${board.content}</textarea>
</p>
<p>
<button type="submit">${board.id == null ? '등록' : '수정'}</button>
<a href="/boards">목록으로</a>
</p>
</form>
boardDetail.jsp(상세조회)
<h2>${board.title}</h2>
<p>
ID: ${board.id}
</p>
<p>
내용: ${board.content}
</p>
<p>
<a href="/boards/${board.id}/edit">수정</a>
<form action="/boards/${board.id}/delete" method="POST" style="display:inline;">
<button type="submit" onclick="return confirm('정말로 삭제하시겠습니까?');">삭제</button>
</form>
<a href="/boards">목록으로</a>
</p>
| RUD 동작 | HTTP 메서드 | URI 패턴 | Controller 메서드 | View |
| Create (등록 폼) | GET | /boards/new | showCreateForm | boardForm |
| Create (처리) | POST | /boards | createBoard | redirect:/boards |
| Read (목록) | GET | /boards | listBoards | boardList |
| Read (상세) | GET | /boards/\{id\} | viewBoard | boardDetail |
| Update (수정 폼) | GET | /boards/\{id\}/edit | showEditForm | boardForm |
| Update (처리) | POST | /boards/\{id\} | updateBoard | redirect:/boards/\{id\} |
| Delete (처리) | POST | /boards/\{id\}/delete | deleteBoard | redirect:/boards |

'DEV&OPS > Java' 카테고리의 다른 글
| Spring MVC PostgreSQL CRUD 예제 (0) | 2026.07.16 |
|---|---|
| JWT 인증 (0) | 2026.07.16 |
| 스프링 부트 웹 애플리케이션에서 카카오 로그인 처리 (0) | 2026.07.16 |
| JPA 연관 관계 (0) | 2026.07.11 |
| 인텔리제이 주석 템플릿 설정 (0) | 2026.07.06 |
