
한 번에 보는 REST → JSON → 응답 타입 → ResponseEntity → Validation → HATEOAS → Swagger
| Method | 의미 |
|---|---|
GET | 조회(Read) |
POST | 생성(Create) |
PUT | 전체 수정(Update) |
DELETE | 삭제(Delete) |
/)는 계층, 마지막 슬래시 금지.Accept 헤더)./users/{id}/orders."key": value 쌍, 콤마(,) 구분.{
"name": "홍길동",
"age": 18,
"address": "서울특별시"
}
@RestController 응답 타입 모음@RestController
@RequestMapping("/response")
public class ResponseRestController {}
@GetMapping("/hello")
public String hello() { return "hello world"; }
@GetMapping("/random")
public int random() { return (int)(Math.random()*10)+1; }
@AllArgsConstructor @Getter
class Message { int httpStatusCode; String message; }
@GetMapping("/message")
public Message getMessage() { return new Message(200,"메세지"); }
@GetMapping("/list")
public List<String> getList(){ return List.of("사과","바나나","복숭아"); }
@GetMapping("/map")
public Map<Integer,String> getMap(){
return Map.of(200,"정상",404,"없음",500,"서버오류");
}
@GetMapping(value="/image", produces=MediaType.IMAGE_PNG_VALUE)
public byte[] image() throws IOException {
return getClass().getResourceAsStream("/images/sample.PNG").readAllBytes();
}
ResponseEntity@GetMapping("/entity")
public ResponseEntity<Message> entity(){
return ResponseEntity.ok(new Message(123,"hello world"));
}
ResponseEntity 실전 패턴@AllArgsConstructor @Getter @Setter
class UserDTO { int no; String id; String pwd; String name; Date enrollDate; }
@AllArgsConstructor @Getter @Setter
class ResponseMessage { int httpStatus; String message; Map<String,Object> results; }
@GetMapping("/users")
public ResponseEntity<ResponseMessage> findAll(){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application","json", StandardCharsets.UTF_8));
Map<String,Object> body = Map.of("users", users);
return new ResponseEntity<>(new ResponseMessage(200,"조회 성공", body), headers, HttpStatus.OK);
}
@GetMapping("/users/{userNo}")
public ResponseEntity<ResponseMessage> findOne(@PathVariable int userNo){
UserDTO user = users.stream().filter(u->u.getNo()==userNo).toList().get(0);
return ResponseEntity.ok()
.contentType(new MediaType("application","json", StandardCharsets.UTF_8))
.body(new ResponseMessage(200,"조회 성공", Map.of("user", user)));
}
@PostMapping("/users")
public ResponseEntity<?> create(@RequestBody UserDTO newUser){
newUser.setNo(users.getLast().getNo()+1);
newUser.setEnrollDate(new Date());
users.add(newUser);
return ResponseEntity.created(URI.create("/entity/users/"+newUser.getNo())).build();
}
@PutMapping("/users/{userNo}")
public ResponseEntity<?> modify(@PathVariable int userNo, @RequestBody UserDTO req){
UserDTO user = users.stream().filter(u->u.getNo()==userNo).toList().get(0);
user.setId(req.getId()); user.setPwd(req.getPwd()); user.setName(req.getName());
return ResponseEntity.created(URI.create("/entity/users/"+userNo)).build();
}
@DeleteMapping("/users/{userNo}")
public ResponseEntity<?> remove(@PathVariable int userNo){
users.removeIf(u->u.getNo()==userNo);
return ResponseEntity.noContent().build();
}
class UserNotFoundException extends Exception {
public UserNotFoundException(String msg){ super(msg); }
}
@AllArgsConstructor @Getter
class ErrorResponse { String code; String description; String detail; }
@ControllerAdvice
class ExceptionController {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handle(UserNotFoundException e){
return new ResponseEntity<>(
new ErrorResponse("ERROR_CODE_00000","회원 정보 조회 실패", e.getMessage()),
HttpStatus.NOT_FOUND);
}
}
의존성
implementation 'org.springframework.boot:spring-boot-starter-validation'
DTO에 제약
@AllArgsConstructor @Getter @Setter
class UserDTO {
int no;
@NotNull(message="아이디는 반드시 입력") @NotBlank(message="아이디는 공백 불가") String id;
String pwd;
@NotNull(message="이름 필수") @Size(min=2, message="이름 2글자 이상") String name;
@Past Date enrollDate;
}
Controller에서 활성화
@PostMapping("/users")
public ResponseEntity<?> create(@Validated @RequestBody UserDTO user){ ... }
검증 예외 처리
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValid(MethodArgumentNotValidException e){
String code="", desc="", detail="";
if(e.getBindingResult().hasErrors()){
var fe = e.getBindingResult().getFieldError();
detail = fe.getDefaultMessage();
switch (fe.getCode()){
case "NotNull" -> { code="ERROR_CODE_00001"; desc="필수 값 누락"; }
case "NotBlank"-> { code="ERROR_CODE_00002"; desc="공백 입력"; }
case "Size" -> { code="ERROR_CODE_00003"; desc="크기 불일치"; }
}
}
return new ResponseEntity<>(new ErrorResponse(code, desc, detail), HttpStatus.BAD_REQUEST);
}
응답에 링크를 포함해 다음 행동을 스스로 탐색하도록 하는 하이퍼미디어 원칙.
의존성
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
예시
@GetMapping("/users")
public ResponseEntity<ResponseMessage> findAll(){
List<EntityModel<UserDTO>> userWithRel = users.stream().map(user ->
EntityModel.of(user,
linkTo(methodOn(HateoasTestController.class).findUserByNo(user.getNo())).withSelfRel(),
linkTo(methodOn(HateoasTestController.class).findAllUsers()).withRel("users")
)
).toList();
return ResponseEntity.ok(new ResponseMessage(200,"조회 성공", Map.of("users", userWithRel)));
}
API 문서 자동화 + 브라우저에서 바로 테스트.
의존성
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
설정
springdoc:
packages-to-scan: com.ohgiraffers.restapi.section05
default-consumes-media-type: application/json;charset=UTF-8
default-produces-media-type: application/json;charset=UTF-8
OpenAPI Bean
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI(){
return new OpenAPI().info(new Info()
.title("Ohgiraffers API")
.description("SpringBoot Swagger 연동 테스트")
.version("1.0.0"));
}
}
컨트롤러 문서화 포인트
@Tag(name="Spring Boot Swagger 연동 (user)")
@Operation(summary="전체 회원 조회", description="전체 회원 목록을 조회한다.")
@GetMapping("/users") ...
접속: http://{host}:{port}/swagger-ui/index.html
200/201/204/400/404/500 등.ResponseEntity로 헤더/상태/바디를 명시적으로 제어.