스프링 부트 그림으로 요약한 요청과 응답에 흐름 살펴 보기

- 클라이언트 측에서 /user로 GET 요청을 합니다. 이 요청은 톰캣을 거쳐 스프링 부트 애플리케이션으로 전달됩니다.
- 이때, 스프링 부트의 디스패처 서블릿(DispatcherServlet)이 요청을 받습니다. 디스패처 서블릿은 요청된 URL을 분석하고, 이를 처리할 수 있는 컨트롤러와 해당 메서드를 찾습니다.
- 컨트롤러에서 비즈니스 로직이 필요한 경우, 비즈니스 계층(Service) 및 퍼시스턴스 계층(Repository)을 통해 데이터를 처리합니다.
- 응답 과정에서 MIME 타입(예: JSON, HTML, XML 등)에 따라 다음과 같이 응답 방식이 달라집니다:
- JSON, XML 등 데이터 포맷으로 응답해야 하는 경우, 메시지 컨버터가 작동합니다. 예를 들어, @ResponseBody가 붙은 메서드는 메시지 컨버터를 사용하여 자바 객체를 JSON 또는 XML로 변환합니다.
- HTML 등의 뷰 템플릿이 필요한 경우, 뷰 리졸버(View Resolver)가 작동하여, 템플릿 엔진(예: Thymeleaf, JSP)을 통해 HTML 문서를 생성하여 클라이언트에게 반환합니다.
GET 방식과 URL 주소 설계
멱등성(Idempotency)이란 같은 작업을 여러 번 수행해도 결과가 달라지지 않는 성질을 의미합니다. 이 개념은 특히 HTTP 메서드나 데이터베이스 연산에서 자주 사용됩니다.
멱등성을 가짐 (GET, PUT, DELET )
비 멱등성을 가짐 (POST)
멱등성의 의미?
- 안정성: 멱등성을 가진 연산은 중복 요청에 대해 안전합니다. 네트워크 문제로 인해 동일한 요청이 여러 번 전달되더라도, 최종 결과는 변하지 않으므로 안전하게 처리할 수 있습니다.
- 데이터 일관성: 멱등성은 시스템이 일관된 상태를 유지하도록 도와줍니다. 특히, 분산 시스템에서 동일한 작업이 여러 번 실행될 가능성이 있을 때, 멱등성은 데이터 무결성을 보장합니다.
멱등성이란 같은 작업을 여러 번 수행해도 결과가 달라지지 않는 성질을 의미하며, 주로 HTTP 메서드나 데이터베이스 연산에서 사용됩니다. 멱등성을 가진 연산은 안정적이고 예측 가능하며, 시스템의 일관성을 유지하는 데 중요한 역할을 합니다.
쿼리 스트링(Query String)과 경로 매개변수(Path parameter)는 모두 HTTP 요청에서 파라미터를 전달하는 방식입니다. 그러나 다음과 같은 차이점이 있습니다.
쿼리 스트링(Query String)
- URL 뒤에 '?'를 붙이고 파라미터를 key-value 쌍으로 전달합니다.
- 파라미터는 '&'로 구분되며, '='로 key와 value를 구분합니다.
- 브라우저의 캐시와 검색 엔진에서 높은 가중치를 부여합니다.
- 파라미터를 전달할 때 key와 value를 쌍으로 전달하기 때문에, 순서를 변경해도 문제가 없습니다.
- 예시 : http://example.com/search?q=keyword&page=1
경로 매개변수(Path parameter)
- URL 경로의 일부로 파라미터를 전달합니다.
- 경로 변수(Path variable)를 사용하여 파라미터를 전달하며, **{}**로 변수를 감싸서 표시합니다.
- 일반적으로 RESTful API에서 사용되며, URL 자체가 파라미터 정보를 전달합니다.
- 파라미터를 전달할 때 key와 value를 쌍으로 전달하지 않습니다.
- 예시 : http://example.com/users/{id}
package com.example.demo1.controller;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* GET 방식 요청 주소 설계와 핸들러 처리를
* 학습 한다.
*
*/
// IoC 의 대상 -- 스프링 프레임워크가 자동으로 new 해 줌
@Controller // 스프링 프레임워크 안에 --> 뷰 리졸브 --> 해당 경로에 파일 찾는 일을 함
// @RestController // 데이터를 반환 하는 일을 함
//@RestController // @Controller + @ResponsBody
public class GetApiController {
// GET 방식으로 요청을 하면 처리하는 메서드를 만들어 두어야 한다.
// 주소 설계 - http://localhost:8080/hello
@GetMapping("/hello")
@ResponseBody // 야 파일 경로를 찾는게 아니라 그냥 데이터를 반환 해
public String hello() {
// index.mustache <--- 파일 찾기
return "index";
}
/**
* 쿼리스트링 방식 (@RequestParam)
* 주소설계
* http://localhost:8080/qs1?name=둘리
* @param name=value
* @return String
*/
@GetMapping("/qs1")
public String qs1(@RequestParam(name = "name") String name) {
return "응답받은 name키값의 값은 = " + name;
}
/**
* 웹브라우저 주소창에 작성하는 주소 (Get 요청 방식)
* 쿼리 스트링 방식
* 주소 설계
* http://localhost:8080/qs2?name=둘리&age=10
* required = false, defaultValue = "고길동"
*/
@GetMapping("qs2")
public String qs2(@RequestParam(name = "name") String name,
@RequestParam(name = "age",
required = false, defaultValue = "0") int age) {
System.out.println("name : " + name);
System.out.println("age : " + age);
return "name="+name+"&age="+age;
}
/**
* 쿼리스트링 방식(@RequestParam)
* 주소 설계
* http://localhost:8080/qs3?name=둘리&age=10&groupId=com.tenco
* @param name, age, groupId
* @return String
* 직접 설계 먼저
*/
@GetMapping("/qs3")
public String qs3(@RequestParam(name = "name")String name,
@RequestParam(name = "age")Integer age,
@RequestParam(name = "groupId")String groupId) {
System.out.println("name:"+name);
System.out.println("age:"+age);
System.out.println("groupId:"+groupId);
return "name:"+name + "&age:"+age +"&groupId:"+groupId;
}
// 주소 설계 :
// http://localhost:8080/qs4
@GetMapping("/qs4")
@ResponseBody
public User helloObject() {
// 응답시에 데이터를 반환 단 (User Object로 내려 보자)
// Object (서버 측) 응답시킬 때 -- 잭슨, Gson
// new User("마이콜", 20); --> 문자열로 변환 시키기 위해서
// 반드시 @Getter 가 있어야 한다.
// MappingJackson2HttpMessageConverter 동작 함
return new User("마이콜", 20);
}
// 내부 클래스
@AllArgsConstructor
@Getter
class User {
private String name;
private Integer age;
}
// http://localhost:8080/qs5?a=둘리&b=10&c=com.tenco
@GetMapping("/qs5")
@ResponseBody
public String qs5(@RequestParam Map<String, String> data) {
// Map 방식으로 동적으로 들어오는 키와값을 받아서 처리해 보자.
StringBuffer sb = new StringBuffer();
data.entrySet().forEach(entry -> {
System.out.println(entry.getKey() + " = " + entry.getValue());
sb.append(entry.getKey()+"="+entry.getValue()+"\n");
});
return sb.toString();
}
} // end of outer class
POST 방식의 이해
POST: 멱등성을 가지지 않는 대표적인 HTTP 메서드입니다.
예를 들어, POST/users 로 새로운 사용자를 생성하는 요청을 여러 번 보내면, 매번 새로운 사용자가 생성됩니다. 즉, 같은 요청을 여러 번 보내면 결과가 달라집니다.
JSON 데이터 타입 확인
- 문자열 ("name": "John")
- 숫자 ("age": 30)
- 불리언 ("isStudent": false)
- 객체 ("address": { "city": "New York", "zipCode": "10001" })
- 배열 ("hobbies": ["reading", "traveling", "swimming"])
- null ("middleName": null)
{
"name": "John",
"age": 30,
"isStudent": false,
"address": {
"city": "New York",
"zipCode": "10001"
},
"hobbies": ["reading", "traveling", "swimming"],
"middleName": null
}
PostApiController 생성
package com.example.demo1.controller;
import com.example.demo1.dto.UserDTO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController // IoC 대상이 된다 (프레임워크가 직접 객체 단 하나만 생성 한다)
@RequestMapping("/post") // 해당 컨트롤러 객체의 대문 (공통 주소 사용)
public class PostApiController {
/**
* 주소설계
* POST 방식
* http://localhost:8080/post/demo1
* Http body = {"name" : "둘리", "age" : 11}
*/
@PostMapping("/demo1")
public String demo1(@RequestBody Map<String, Object> reqData) {
StringBuffer sb = new StringBuffer();
reqData.entrySet().forEach( e -> {
System.out.println("key : " + e.getKey() + " = " + e.getValue());
sb.append(e.getKey() + "=" + e.getValue());
});
return sb.toString();
}
/**
* 주소설계
* POST 방식
* http://localhost:8080/post/demo2
* Http body = {"name" : "둘리", "age" : 11}
* [Post 는 요청에 본문 있다 - Object로 파싱해보기]
* DTO -
*/
@PostMapping("/demo2")
// 스프링 프레임 워크가 본문에서 값을 읽어서 UserDTO 객체를 생성한다.
//public String demo2(@RequestBody UserDTO userDto) {
public UserDTO demo2(@RequestBody UserDTO userDto) {
System.out.println(userDto);
System.out.println(userDto.getName());
System.out.println(userDto.getAge());
System.out.println(userDto.getPhoneNumber());
return userDto;
}
}
PUT 방식의 이해
PUT 방식은 HTTP 메서드 중 하나로, 리소스를 생성하거나 업데이트할 때 사용됩니다. 주로 RESTful API에서 특정 리소스를 완전히 대체하거나 수정할 때 사용됩니다.
주요 특징
- 전체 업데이트:
- PUT 요청은 클라이언트가 서버에 특정 리소스의 전체 데이터를 보내 해당 리소스를 완전히 대체하거나 새로 생성하는 요청입니다. 즉, 리소스의 일부가 아닌 전체 데이터를 전송하여 갱신합니다.
- 멱등성:
- PUT 요청은 멱등성을 가집니다. 즉, 동일한 요청을 여러 번 보내도 결과가 동일합니다. 예를 들어, 동일한 데이터를 PUT 요청으로 여러 번 보내도 리소스 상태는 변하지 않습니다.
- 리소스 식별:
- PUT 요청은 URL을 통해 업데이트할 리소스를 명확히 식별합니다. 예를 들어, /users/1 URL로 PUT 요청을 보내면, ID가 1인 사용자를 업데이트하거나, 해당 리소스가 존재하지 않으면 새로 생성합니다.
요청 데이터
{
"name" : "mike",
"age" : 30,
"car_list" : [
{
"name" : "M3",
"car_number" : "22너 3341"
},
{
"name" : "R8",
"car_number" : "33너 1234"
}
]
}
DTO 설계
package com.example.demo1.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.ToString;
import java.util.List;
@Getter
@ToString
public class UserDTO2 {
private String name;
private Integer age;
@JsonProperty("car_list")
private List<CarDTO> carList;
// 내부 클래스
@Getter
@ToString
static class CarDTO {
private String name;
@JsonProperty("car_number")
private String carNumber;
}
}
package com.example.demo1.controller;
import com.example.demo1.dto.UserDTO;
import com.example.demo1.dto.UserDTO2;
import org.springframework.web.bind.annotation.*;
@RestController // IoC 대상이 된다.
@RequestMapping("/put")
public class PutApiController {
/**
* 주소 설계
* METHOD - PUT
* http://localhost:8080/put/demo1
*/
@PutMapping("/demo1")
public UserDTO2 put1(@RequestBody UserDTO2 userDTO2) {
System.out.println(userDTO2.toString());
// 리턴타입 Object 내려 주고 있다 -->
// MappingJackson2HttpMessageConverter 객체가 문자열(json) 변환해서 던진다.
return userDTO2;
}
}
DELETE 방식의 이해

package com.example.demo1.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
// 컴포넌트 스캔에 대상 --> IoC(제어의 역전) --> 스프링 컨테이너 안에 싱글톤 패턴으로 관리 됨
public class DeleteApiController {
/**
* DELETE 요청 처리
* 웹 브라우저 주소창에서 사용이 불가
* HTTP 요청 메시지에 본문이 없다
* 주소 설계
* http://localhost:8080/delete/100?account=우리은행
*/
@DeleteMapping("/delete/{userId}")
public ResponseEntity<?> delete(@PathVariable(name = "userId") String userId,
@RequestParam(name = "account") String account) {
System.out.println("userId : " + userId);
System.out.println("account : " + account);
// ResponseEntity responseEntity = new ResponseEntity();
// HTTP - 404 <--- 페이지를 찾을 수 없음
return ResponseEntity.status(HttpStatus.OK).body("정상삭제완료");
}
}