"Controller & RestController"
백엔드와 프론트엔드의 역할이 분리됨에 따라 프론트엔드는 화면에 그려낼 Data가 필요한데(프론트는 Data를 가지고 있지 않음),
백엔드는 주로 이 Data를 JSON형식으로 내려준다.

(Model 대신 JSON 데이터를 프론트에게 응답하는 경우)
1. 클라이언트가 Request를 보내면 DispatcherServlet이 받음.
2. DispatcherServlet은 이 요청을 Handler Mapping을 통해서 해당 Controller에게 보냄
3. 해당 Controller가 이 요청을 처리하고 데이터(JSON형태)를 정제해서 보내줌
4. JSON 데이터를 DispatcherServlet이 클라이언트에게 Response로 내려가게 됨
1. @Controller 어노테이션
전통적인 Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용한다.
2. @RestController 어노테이션
@RestController는 @Controller에 @Response Body가 추가된 것이다.
즉, @Controller가 주로 View를 반환할 때 쓰이지만, 만약에 @Response Body와 같이 쓴다면
@RestController와 똑같은 역할을 수행할 수 있다는 것이다.
@Response Body란?
- Spring MVC 컨트롤러의 메서드에 사용되는 어노테이션
- 이 어노테이션이 붙은 메서드가 반환하는 값은 View를 찾는데 사용되지 않고, HTTP 응답 본문(Response Body)에 직접 작성된다.
- 즉, JSON형식으로 응답을 내려줄때 사용하는 것이 @Response Body 어노테이션이다.
"요청 매핑 어노테이션"
1. 기본 매핑 어노테이션(@RequestMapping)
특정 URL로 Request를 보내면 들어온 요청을 Controller 내부의 특정 Method와 Mapping하기 위해 사용한다.
- 사용법 1
- 해당 컨트롤러의 전체 경로 prefix를 설정한다.
- prefix >> "접두"(접할 접, 머리 두) 한마디로 "가장 앞에 붙는 경로"
@RestController
@RequestMapping("/hello")
public class HelloController {
// ...
}
이런 식으로 가장 먼저 오는 경로로 설정해줄 때 사용하고,
@RequestMapping("/hello") >> /hello가 가장 먼저 오는 경로로 설정
- 사용법 2
- 특정 API의 전체 경로를 허용
@RestController
public class HelloController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public void getHello() {
// ...
}
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public void postHello() {
// ...
}
@RequestMapping(value = "/hello", method = RequestMethod.PUT)
public void putHello() {
// ...
}
@RequestMapping(value = "/hello", method = RequestMethod.PATCH)
public void patchHello() {
// ...
}
@RequestMapping(value = "/hello", method = RequestMethod.DELETE)
public void deleteHello() {
// ...
}
}
GET, POST, PUT, PATCH, DELETE 메서드를 설정해주고, 어떤 경로로 맵핑 해줄 것인지를 설정해주는 것이
@RequestMapping의 2번째 사용법이다.
사용법 2와 같은 방법은 거의 쓰이지 않으며, 대부분 사용법 1의 용도로 @RequestMapping 어노테이션을 활용한다.
사용법2는 HTTP 메서드별 전용 어노테이션으로 대체한다.
"HTTP 전용 메서드별 전용 어노테이션"
주로 사용하는 HTTP 메서드로는 GET, POST, PUT, PATCH, DELETE가 있다.
(위에 나온 사용법2를 대체하는 어노테이션)
- GET
- @GetMapping을 사용
@RestController
public class HelloController {
@GetMapping("/hello")
public void getHello() {
// ...
}
}
- POST
- @PostMapping을 사용
@RestController
public class HelloController {
@PostMapping("/hello")
public void postHello() {
// ...
}
}
- PUT
- @PutMapping을 사용
@RestController
public class HelloController {
@PutMapping("/hello")
public void putHello() {
// ...
}
}
- PATCH
- @PatchMapping을 사용
@RestController
public class HelloController {
@PatchMapping("/hello")
public void patchHello() {
// ...
}
}
- DELETE
- @DeleteMapping을 사용
@RestController
public class HelloController {
@DeleteMapping("/hello")
public void deleteHello() {
// ...
}
}
"데이터 전달 어노테이션"
1. 쿼리 파라미터 (Query Parameter, Query String)
2. 폼 데이터 (Form Data)
3. 경로 변수 (Path Variable)
4. 요청 본문 (Request Body)
1, 쿼리 파라미터 (@Request Param)
쿼리 파라미터는 URL의 ? 뒤에 오는 쿼리 파라미터들을 처리할 때 사용
예를 들어, GET/users?name = john & age 요청이 있다면, 이 중에서 ? 뒤에 오는
name = john,
age = 25
이것이 쿼리파라미터이다.(쿼리 스트링)
- 기본 사용법
@RestController
public class UserController {
// /users?name=john&age=30 요청이 들어온 상황이라면
@GetMapping("/users")
public String getUser(
@RequestParam String name, // name 값은 john
@RequestParam int age // age 값은 30
) {
// ...
}
}
/users?name=john&age=30 요청이 GET으로 들어온 상황이라고 가정해보면,
name = john이고, age = 30이라는 것을 꺼내서 쓸 수 있어야 한다.
실제 API의 주소는 /users까지기 때문에, @GetMapping("/users")라고 작성하고,
쿼리파라미터 john과 age를 꺼내와올 때는 @RequestParam을 사용한다.
@RequestParam String name, >> 이렇게 하면 john이 name이라는 변수 안에 Mapping됨.
@RequestParam int age; >> 30이라는 값이 age라는 변수 안에 Mapping됨
- 선택적 파라미터와 기본값 설정
@RestController
public class UserController {
// /users?page=1&size=10 요청이 들어온 상황이라면
@GetMapping("/users")
public List<String> getUsers(
@RequestParam(defaultValue = "0") int page, // 1
@RequestParam(defaultValue = "10") int size, // 10
@RequestParam(required = false) String sort // null
) {
// ...
}
}
/users?page=1&size=10 요청이 GET으로 들어온 상황이라고 가정해보면,
Query Parameter는 들어오지 않아도 똑같은 API와 다름이 없다. 즉, ?page=1&size=10 이 쿼리파라미터가 없어도
@GetMapping("/users")에 들어오게 된다. Query Parameter는 있을 수도 있고, 없을 수도 있기 때문이다.
만약 쿼리파라미터가 없고 /users만 들어온 상태여도 defaultValue를 설정해줬기 때문에 괜찮다.
@RequestParam(defaultValue = "0") int page, >> defaultValue가 0이기 때문에, 만약 안들어온다면 1이 아니라 0으로 Mapping이 된다.
required = false로 설정하지 않으면 파라미터가 필수가 된다.
@RequestParam의 default required 값은 true이기 때문이다.
defaultValue를 설정하면 자동으로 required = false가 된다.
2. 폼데이터 or 쿼리파라미터 여러 개(@ModelAttribute)
HTML 폼 or 쿼리 파라미터 여러 개를 객체로 바인딩 할 때 사용한다,
@RequestParam을 여러 개 쓰는 대신 객체 하나로 받을 수 있다.
- @RequestParam 여러 개를 @ModelAttribute 하나로 처리
@RequestParam을 여러개 사용
// @RequestParam이 너무 많은 경우
@GetMapping("/api/search")
public List<String> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false) String email,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
// 파라미터가 많아서 복잡함!
}
// @ModelAttribute 사용 (추천)
@GetMapping("/api/search")
public List<String> searchUsers(@ModelAttribute UserSearchDto dto) {
// 하나의 @ModelAttribute로 데이터를 받을 수 있다.
}
DTO (Data Tansfer Object) - 데이터를 옮기는 객체 (데이터를 Mapping해주기 위한, 담아주기 위한 클래스)
DTO클래스를 만들고, 위에 코드에서 맨 밑부분을 보면,
@ModelAttribute UserSearchDTO dto >> @ModelAttribute 어노테이션을 활용해 6개의 @RequestParam 필드들을
DTO 클래스로 한번에 묶어서 객체로 처리할 수 있게 된다.
// 바인딩용 DTO 클래스
public class UserSearchDto {
private String name;
private String email;
private Integer minAge;
private Integer maxAge;
private Integer page = 0; // 기본값 설정 가능
private Integer size = 10; // 기본값 설정 가능
// ...
}
3. 경로 변수(@PathVariable)
URL의 경로의 일부를 변수로 받을 때 사용
GET/users/123 형태의 요청에서 123값을 받을 수 있다.
쿼리파라미터와 혼동하지 않도록 주의!
- 기본 사용법
@RestController
public class UserController {
// GET /users/123
@GetMapping("/users/{userId}")
public String getUser(@PathVariable Long userId) { // userId는 123
// ...
}
}
쿼리파라미터와 다른 점이, @GetMapping("/users/{userId}") >> { } 중괄호를 사용하여 받고 싶은 변수 명을 적어준다.
그 다음 @PathVariable Long userId >> 이렇게 해주면 userId 변수 안에 123이라는 값이 Mapping된다.
4. 요청 본문 (Request Body)
HTTP 요청의 본문(Body)에 있는 데이터를 객체로 전환할 때 사용한다.
주로 JSON형태의 데이터를 받을 때 사용하며, REST API에서 가장 많이 사용된다.
@RestController
public class UserController {
// POST /users
// Body: {"name": "John", "email": "john@example.com", "age": 30}
@PostMapping("/api/users")
public String createUser(@RequestBody CreateUserRequest request) {
// ...
}
}
위에 코드에서 Body : {"name" : "John", "email" : "john@example.com", "age" : 30} 이러한 JSON형태를 바디로 보낸다면,
@RequestBody로 받을 수 있다. 대신 Body의 값들은 객체이므로, 객체의 형태를 DTO클래스로 정의해줘야한다.
DTO 클래스 (CreateUserRequest)
@Getter
public class CreateUserRequest {
// 위의 요청의 경우 아래와 같이 값이 매핑됩니다.
private String name; // John
private String email; // john@example.com
private int age; // 30
}
이렇게 @RequestBody 어노테이션을 활용하여 데이터를 받아 올 수 있다.
"PathVariable과 RequestParam은 언제 어떤 것을 써야할까?"
- PathVariable을 사용하는 경우
- @PathVariable은 URL 경로 자체에 값이 포함되어 있기 때문에 반드시 값이 있어야 한다.
- -> PathVariable은 생략되면 안되는 필수값이다.
GET /posts/123
GET /posts
만약, posts/123에서 /123을 삭제하면, /posts가 되어버리므로, 서로 다른 API가 된다.
- @RequestParam을 사용하는 경우
- @RequestParam은 쿼리 파라미터를 받아오게 되는데 각 Key / Value값은 조건부이다. 즉, 있어도 되고 없어도 되는 값일 때 사용한다. 주로 검색용에 많이 쓰인다.
- -> 쿼리 파라미터가 있든 없든 동일한 API이다.
GET /users?nickname=스파르타
GET /users
GET /users?address=서울시
이 3개의 API는 다 같은 API이다, 그 이유는 @RequestParam(쿼리 스트링)을 사용하고 있는데, 쿼리 스트링은 조건부, 즉 있어도 되고 없어도 되는 값이므로, ?nickname = 스파르타, ?address = 서울시 이 두개의 쿼리 파라미터를 지워도
GET/users는 다 똑같다. 단지 조건(쿼리 파라미터)만 다를 뿐이다.
PathVariable은 생략될 수 없는 필수값 이기 때문에 삭제시 완전 다른 API가 되버리고, RequestParam의 쿼리 파라미터는 조건부이기 때문에 조건만 다를뿐, 다 같은 API이다.
***
RequestParam은 조건부이다, 즉 있어도 되고 없어도 되는데, 하지만 스프링에서 @RequestParam은 default로 required = ture로 되어 있기 때문에, 내가 @RequestParam에 대한 Key / Value를 조건부로 있어도 되고, 없어도 되게 설정을 하고 싶으면, 반드시 required = false라는 옵션을 작성 해야지만 진정한 조건부로 사용할 수 있다!