가수면

Spring Boot로 REST API 만들기 정리 본문

Java

Spring Boot로 REST API 만들기 정리

니비앙 2023. 12. 19. 23:30

정적 데이터 기준

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")

'Java' 카테고리의 다른 글

HATEOAS  (1) 2023.12.26
Swagger  (0) 2023.12.26
[Java] 심화  (0) 2023.12.12
JSTL  (0) 2023.12.12
JSP  (0) 2023.12.02
Comments