가수면
Spring Boot로 REST API 만들기 정리 본문
정적 데이터 기준
GET
1-1. 베이스가 되는 클래스 생성
1-2. 필드로 사용될 변수들 지정
1-3. 필드 생성, 게터와 세터, toString 생성
일반적으로 entity를 만들 때 생성자를 명시적으로 생성하지 않더라도 컴파일러가 기본 생성자를 자동 생성해주지만, 만약 사용자 정의 생성자가 하나라도 존재한다면 아래처럼 기본 생성자를 명시적으로 적어줘야 한다.
public Todo() {}
JPA 구현체는 리플렉션(런타임 시점에 클래스의 정보를 조회하거나 수정할 수 있게 해주는 기능)을 사용하여 엔티티를 관리하게 된다, 이 과정에서 파라미터가 없는 기본 생성자를 호출하게 되는데, 만약 사용자 정의 생성자만 있고 기본 생성자가 없다면 JPA 구현체는 해당 엔티티의 인스턴스를 생성할 수 없게 된다.
2-1. service 로직을 담는 클래스 생성
2-2. @Component 혹은 @Service 어노테이션 지정
2-3. 필드, 생성자 생성해 의존성 주입
2-4. api를 통해 반환시킬 로직을 담은 메소드 생성
3-1. api를 담당할 컨트롤러 클래스 생성
3-2. @RestController 어노테이션 지정
3-3. 필드와 생성자를 생성해 의존성 주입
3-4. @GetMapping 어노테이션으로 get요청에 사용할 메소드 생성
예시)
@RestController
public class UserResource {
private UserDaoService service;
public UserResource(UserDaoService service) {
this.service = service;
}
@GetMapping("/users/{id}")
public User retrieveUser(@PathVariable int id) {
return service.findOne(id);
}
}
에러 처리
잘못된 url 요청에 대한 응답
1-1. .orElse() 사용 - .get()의 옵셔널 체이닝 같은 것
public User findOne(int id) {
Predicate<? super User> predicate = user -> user.getId().equals(id);
return users.stream().filter(predicate).findFirst().orElse(null); // .get()을 수정
}
1-2. 분기 처리
@GetMapping("/users/{id}")
public User retrieveUser(@PathVariable int id) {
User user = service.findOne(id);
if(user==null)
throw new UserNotFoundException("id:"+id);
return user;
}
1-3. 분기처리에 사용할 클래스 생성
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
1-3. 예외 발생 시 반환되는 에러 응답 구조 설정
예시) 에러 발생 시간, 메세지,상세 정보를 반환하고자 하는 경우
// ErrorDetails 클래스
public class ErrorDetails {
private LocalDateTime timestamp;
private String message;
private String details;
생성자
게터
}
// CustomizedResponseEntityExceptionHandler 클래스
@ControllerAdvice // exception handler에 사용되는 어노테이션
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
// ResponseEntityExceptionHandler의 handleException 메소드를 커스텀
@ExceptionHandler(Exception.class) // 모든 예외 지정 (어떤 예외를 처리할 건지)
public final ResponseEntity<ErrorDetails> handleAllException(Exception ex, WebRequest request) throws Exception {
ErrorDetails errorDetails = new ErrorDetails(LocalDateTime.now(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<ErrorDetails>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); // 상태 코드 지정
}
@ExceptionHandler(UserNotFoundException.class) // 에러처리를 UserNotFoundException클래스가 담당할 경우
public final ResponseEntity<ErrorDetails> handleUserNotFoundException(Exception ex, WebRequest request) throws Exception {
ErrorDetails errorDetails = new ErrorDetails(LocalDateTime.now(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<ErrorDetails>(errorDetails, HttpStatus.NOT_FOUND);
}
}
유효성을 통과하지 못한 요청에 대한 응답
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
// User 클래스
public class User {
private Integer id;
@Size(min = 2, message = "Name should have atleast 2 characters")
private String name;
@Past(message = "Birth Date should be in the past")
private LocalDate birthDate;
// CustomizedResponseEntityExceptionHandler 클래스
@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
.
.
.
// ResponseEntityExceptionHandler에 있는 handleMethodArgumentNotValid를 오버라이드해서 커스텀
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
// 1. 기본 메세지
String defaultMessage = ex.getMessage();
// 2. 내가 설정한 메세지만 보여주고 싶을 때
String customMessage = ex.getFieldError().getDefaultMessage();
// 3. 2번에서 에러가 여러 개일 수 있는 경우
String customMessages = ex.getFieldErrors().stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.collect(Collectors.joining(", "));
ErrorDetails errorDetails = new ErrorDetails(LocalDateTime.now(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity(errorDetails, HttpStatus.BAD_REQUEST);
}
POST
1-1. service 로직을 담은 클래스에 요청을 수행할 메소드 생성
1-2. @PostMapping 어노테이션 지정
1-3. @RequestBody 설정
1-4. ResponseEntity를 이용해 상태 코드 지정
1-5. 응답 헤더에 실어 보낼 반환 주소 지정
예시)
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = service.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest() // 현재 요청에 해당하는 URL를 반환
.path("/{id}") // URL에 추가
.buildAndExpand(savedUser.getId()) // id 부분은 바꿔줌
.toUri(); // 값을 URI로 변경
// location대신 null을 입력하면 응답 헤더에 아무것도 안 실림
// location을 지정해줄 경우 생성된 리소스의 uri값을 응답값 헤더에 실어서 반환할 수 있음
// ex) http://localhost:8080/users/4
return ResponseEntity.created(location).build();
}
DELETE
1-1. 삭제 API 컨트롤러 만들기
어노테이션 예시) @DeleteMapping("/users/{id}")
1-2. 서비스 로직 만들기
1-1, 1-2 둘 다 void면 됨
Mysql 연결
1. 의존성 설치
2. application.properties 설정
3. model에 @Entity 설정, id에 @Id, @GeneratedValue 설정, 빈 생성자 설정
4. Entity를 관리하는 JpaRepository 인터페이스 생성
5. 컨트롤러에서 서비스 로직을 사용하던 걸 repository를 사용하도록 수정
JpaRepository에서 제공하는 메소드를 사용하면서 서비스 로직을 작성할 필요가 없어짐
XML 형태로 응답 반환하기
XML 표현과 Swagger 문서는 다른 고급 기능을 다룰 때 충돌을 일으킬 수 있음
1. 서버에 의존성 설치하기
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2. 요청 헤더에 Accept:application/xml 설정하기
Accept:application/json이 디폴트
국제화
1. src/main/resources 패키지 안에
messages.properties
messages_nl.properties
같은 형식의 파일을 생성
2. 생성자 주입
private MessageSource messageSource;
public HelloWorldController(MessageSource messageSource) {
this.messageSource = messageSource;
}
3. messageSource.getMessage 사용하는 api 컨트롤러 생성
@GetMapping(path = "/hello-world-internationalized")
public String helloWorldInternationalized() {
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage("good.morning.message", null, "Default Message", locale );
//return "Hello World V2";
//1:
//2:
// - Example: `en` - English (Good Morning)
// - Example: `nl` - Dutch (Goedemorgen)
// - Example: `fr` - French (Bonjour)
// - Example: `de` - Deutsch (Guten Morgen)
}
이때 getMessage는 3번째 거 사용
4. Accept-Language: 언어 형태로 헤더를 설정해 사용
버전 관리
URL 버전 관리
URL 오염 문제
캐싱 이점을 가짐
@GetMapping("/v1/person")
@GetMapping(path = "/person", params = "version=1")
헤더 버전 관리
헤더 오용 문제
/person/header
X-API-VERSION:1
@GetMapping(path = "/person/header", headers = "X-API-VERSION=1")
/person/accept
Accept:application/vnd.company.app-v1+json
@GetMapping(path = "/person/accept", produces = "application/vnd.company.app-v1+json")