기존 JDBC 프로젝트를 Spring Framework 기반으로 발전시키면서 Spring MVC(Model-View-Controller) 구조를 직접 구현해 보았다. 이전에는 main() 메서드에서 직접 Repository를 호출하여 CRUD를 수행했지만, 이번 프로젝트에서는 Controller → Service → Repository 구조를 적용하여 각 계층의 역할을 분리하였다.
또한 JSP를 View로 사용하면서 사용자의 요청(Request)이 어떻게 Controller로 전달되고, Controller가 Service와 Repository를 거쳐 데이터를 조회한 후 다시 View로 전달하는지 전체적인 흐름을 이해할 수 있었다.
Spring Boot를 사용할 때는 이러한 과정이 자동으로 이루어진다고만 생각했는데, 이번 실습을 통해 MVC 패턴이 왜 필요한지, 그리고 각 계층이 어떤 역할을 수행하는지를 보다 깊게 이해할 수 있었다.
| 학습 내용 | 상세 내용 |
|---|---|
| @Controller | 해당 클래스를 Spring MVC의 Controller로 등록한다. 사용자의 요청을 가장 먼저 처리하는 역할을 수행한다. |
| @RequestMapping | Controller가 처리할 기본 URL을 지정한다. |
| @GetMapping | GET 요청을 처리하는 메서드를 지정한다. 주로 조회 기능에서 사용된다. |
| @PostMapping | POST 요청을 처리하는 메서드를 지정한다. 데이터 생성이나 수정 시 사용된다. |
| Model | Controller에서 View(JSP)로 데이터를 전달하는 객체이다. |
| Redirect | 작업 완료 후 새로운 요청을 발생시키기 위해 사용하는 방식이다. |
@Controller
@RequestMapping("/")
@RequiredArgsConstructor
public class MainController {
private final BankService bankService;
@GetMapping("/")
public String index(Model model){
model.addAttribute(
"accounts",
bankService.getAccounts()
);
return "index";
}
}
이번 실습에서는 사용자가 브라우저에서
http://localhost:8080/
를 요청하면
가장 먼저 DispatcherServlet이 요청을 받는다.
DispatcherServlet은
"/"
URL을 처리할 Controller를 찾는다.
그 결과
@GetMapping("/")
이 실행된다.
Controller에서는
bankService.getAccounts();
를 호출하여
Service에게
"전체 계좌 목록을 가져와."
라고 요청한다.
Controller는
SQL을 작성하지 않는다.
DB와 직접 연결하지도 않는다.
오직
사용자의 요청을 받고
Service를 호출한 뒤
결과를 View로 전달하는 역할만 수행한다.
조회된 데이터는
model.addAttribute()
를 이용하여
JSP에게 전달된다.
model.addAttribute(
"accounts",
bankService.getAccounts()
);
Model 객체는
일종의
Map<String,Object>
처럼 동작한다.
즉,
accounts
라는 이름으로
JSP에게 데이터를 전달하는 것이다.
JSP에서는
${accounts}
를 이용하여 사용할 수 있다.
마지막으로
return "index";
를 반환하면
ViewResolver가
/WEB-INF/views/index.jsp
를 찾아 사용자에게 화면을 보여준다.
브라우저
↓
DispatcherServlet
↓
MainController
↓
BankService
↓
Repository
↓
Database
↓
Repository
↓
Service
↓
Controller
↓
Model
↓
index.jsp
↓
브라우저
이번 실습을 통해
Spring MVC는
각 객체가 자신의 역할만 수행하도록 설계되어 있다는 점을 이해하였다.
@PostMapping("/")
에서는
새로운 계좌를 생성한다.
bankService.makeAccount(dto);
가 호출되면
Service가 Repository에게
INSERT를 요청한다.
그리고
return "redirect:/";
를 수행한다.
처음에는
왜
return "index";
를 하지 않고
redirect:/
를 사용하는지 이해되지 않았다.
하지만
POST 요청 후
바로 JSP를 반환하면
새로고침(F5)을 했을 때
POST 요청이 다시 실행된다.
즉,
계좌가
두 번 생성될 수 있다.
이를
중복 제출(Double Submit)
문제라고 한다.
그래서
POST가 끝나면
다시
GET 요청을 보내도록
Redirect를 사용하는 것이다.
이를
PRG(Post Redirect Get) 패턴
이라고 한다.
| 기능 | 실무 사용 |
|---|---|
| Controller | 모든 요청의 시작점 |
| Model | View 데이터 전달 |
| Redirect | 등록, 수정, 삭제 후 반드시 사용 |
| ViewResolver | Thymeleaf, JSP 등을 찾아준다. |
| DispatcherServlet | 모든 요청을 관리하는 Front Controller |
| 학습 내용 | 상세 내용 |
|---|---|
| @PathVariable | URL에 포함된 값을 변수로 받아오는 기능이다. |
| @ModelAttribute | HTML Form의 데이터를 객체(DTO)로 자동 변환해준다. |
| 계좌 수정 | 수정할 계좌를 조회하고 변경 내용을 저장하였다. |
| 계좌 삭제 | URL을 통해 전달된 id를 이용하여 데이터를 삭제하였다. |
@GetMapping("/{accountId}")
public String showAccount(
@PathVariable long accountId,
Model model
){
AccountViewDTO accountDto =
bankService.findAccount(accountId);
model.addAttribute(
"account",
accountDto
);
return "account";
}
이번 Controller에서는
URL에 포함된 값을
직접 변수로 받아왔다.
예를 들어
/account/3
이라는 요청이 들어오면
Spring은
@PathVariable long accountId
에
자동으로
3
을 넣어준다.
즉,
개발자가
직접
request.getParameter()
를 사용할 필요가 없다.
Spring이 자동으로 처리해준다.
수정 기능에서는
@ModelAttribute
를 사용하였다.
@PostMapping("/{id}")
↓
사용자가 입력한
name=홍길동
↓
Spring
↓
AccountFormDTO
자동 생성
↓
Service 호출
이 과정이 자동으로 이루어진다.
삭제 기능 역시
@GetMapping("/{id}/delete")
를 이용하여
Repository에게
삭제를 요청한다.
Controller는
삭제 SQL을 작성하지 않는다.
Service에게
삭제를 요청할 뿐이다.
사용자
↓
account.jsp
↓
POST /account/3
↓
AccountController
↓
BankService
↓
Repository
↓
UPDATE SQL
↓
DB
↓
Redirect
↓
GET /account/3
예전 JDBC에서는
Scanner scanner
로 데이터를 입력받았다.
하지만
Spring에서는
HTML Form만 작성하면
자동으로
DTO 객체가 생성된다.
즉,
사용자 입력을
직접 파싱할 필요가 없다.
| 기능 | 사용 이유 |
|---|---|
| PathVariable | 상세조회 |
| ModelAttribute | Form 처리 |
| DTO | 사용자 입력 검증 |
| Redirect | 중복 제출 방지 |
| 느낀 점 | 내용 |
|---|---|
| Spring 자동 바인딩 | @ModelAttribute를 통해 Form 데이터가 DTO로 자동 변환되는 과정이 매우 편리하다고 느꼈다. |
| URL 설계 | @PathVariable을 사용하여 URL만으로 필요한 데이터를 전달받는 REST 방식의 장점을 이해하였다. |
| MVC 역할 분리 | Controller는 요청을 연결하는 역할만 수행하고 실제 비즈니스 로직은 Service가 담당한다는 점이 더욱 명확해졌다. |