
스프링 부트를 사용하여 간단한 REST API를 구현해보겠습니다. 이 예제에서는 스테레오타입 어노테이션을 활용하여 컨트롤러, 서비스, 리포지토리를 설정하고, JSON 형식으로 응답하는 REST API를 작성합니다.
프로젝트 구조
스프링 부트 프로젝트 구조는 다음과 같이 설정합니다:
src
└── main
├── java
│ └── com
│ └── example
│ ├── MyApp.java
│ ├── controller
│ │ └── HotelController.java
│ ├── service
│ │ └── HotelService.java
│ ├── repository
│ │ └── HotelRepository.java
│ └── model
│ └── Hotel.java
└── resources
└── application.properties클래스 설명
@SpringBootApplication
스프링 부트 애플리케이션의 시작점입니다.
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}모델 클래스: Hotel
호텔 정보를 담는 모델 클래스입니다.
package com.example.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class Hotel {
@NotNull
private Long id;
@NotBlank
private String name;
@NotBlank
private String location;
private String description;
private Double rating;
// 기본 생성자
public Hotel() {}
// 매개변수 생성자
public Hotel(Long id, String name, String location) {
this.id = id;
this.name = name;
this.location = location;
}
// Getter 및 Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Double getRating() { return rating; }
public void setRating(Double rating) { this.rating = rating; }
}리포지토리 클래스: HotelRepository
호텔 정보를 관리하는 리포지토리 클래스입니다. 간단한 메모리 기반 데이터 저장소를 사용합니다.
package com.example.repository;
import com.example.model.Hotel;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
@Repository
public class HotelRepository {
private final List hotels = new ArrayList<>();
private final AtomicLong idGenerator = new AtomicLong(1);
// 초기 데이터 설정
public HotelRepository() {
hotels.add(new Hotel(idGenerator.getAndIncrement(), "Hilton Hotel", "New York"));
hotels.add(new Hotel(idGenerator.getAndIncrement(), "Marriott Hotel", "Los Angeles"));
}
public List findAll() {
return new ArrayList<>(hotels);
}
public Hotel save(Hotel hotel) {
if (hotel.getId() == null) {
hotel.setId(idGenerator.getAndIncrement());
}
// 기존 호텔 업데이트 또는 새 호텔 추가
Optional existingHotel = findById(hotel.getId());
if (existingHotel.isPresent()) {
int index = hotels.indexOf(existingHotel.get());
hotels.set(index, hotel);
} else {
hotels.add(hotel);
}
return hotel;
}
public Optional findById(Long id) {
return hotels.stream()
.filter(h -> h.getId().equals(id))
.findFirst();
}
public boolean deleteById(Long id) {
return hotels.removeIf(h -> h.getId().equals(id));
}
public List findByLocation(String location) {
return hotels.stream()
.filter(h -> h.getLocation().toLowerCase().contains(location.toLowerCase()))
.toList();
}
}서비스 클래스: HotelService
비즈니스 로직을 처리하는 서비스 클래스입니다.
package com.example.service;
import com.example.model.Hotel;
import com.example.repository.HotelRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class HotelService {
@Autowired
private HotelRepository hotelRepository;
public List getAllHotels() {
return hotelRepository.findAll();
}
public Hotel addHotel(Hotel hotel) {
// 비즈니스 로직 검증
if (hotel.getName() == null || hotel.getName().trim().isEmpty()) {
throw new IllegalArgumentException("호텔 이름은 필수입니다.");
}
if (hotel.getLocation() == null || hotel.getLocation().trim().isEmpty()) {
throw new IllegalArgumentException("호텔 위치는 필수입니다.");
}
return hotelRepository.save(hotel);
}
public Optional getHotelById(Long id) {
return hotelRepository.findById(id);
}
public Hotel updateHotel(Long id, Hotel hotel) {
Optional existingHotel = hotelRepository.findById(id);
if (existingHotel.isPresent()) {
hotel.setId(id);
return hotelRepository.save(hotel);
}
throw new RuntimeException("호텔을 찾을 수 없습니다. ID: " + id);
}
public boolean deleteHotel(Long id) {
if (hotelRepository.findById(id).isPresent()) {
return hotelRepository.deleteById(id);
}
throw new RuntimeException("호텔을 찾을 수 없습니다. ID: " + id);
}
public List getHotelsByLocation(String location) {
return hotelRepository.findByLocation(location);
}
}컨트롤러 클래스: HotelController
클라이언트의 요청을 처리하고 JSON 형식으로 응답하는 컨트롤러 클래스입니다.
package com.example.controller;
import com.example.model.Hotel;
import com.example.service.HotelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/v1/hotels")
@CrossOrigin(origins = "*")
public class HotelController {
@Autowired
private HotelService hotelService;
// 모든 호텔 조회
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity> getAllHotels(
@RequestParam(required = false) String location) {
List hotels;
if (location != null && !location.trim().isEmpty()) {
hotels = hotelService.getHotelsByLocation(location);
} else {
hotels = hotelService.getAllHotels();
}
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(hotels);
}
// 호텔 추가
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity addHotel(@Valid @RequestBody Hotel hotel) {
try {
Hotel savedHotel = hotelService.addHotel(hotel);
return ResponseEntity.status(HttpStatus.CREATED)
.contentType(MediaType.APPLICATION_JSON)
.body(savedHotel);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().build();
}
}
// 특정 호텔 조회
@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity getHotelById(@PathVariable Long id) {
Optional hotel = hotelService.getHotelById(id);
return hotel.map(h -> ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(h))
.orElse(ResponseEntity.notFound().build());
}
// 호텔 정보 수정
@PutMapping(value = "/{id}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity updateHotel(@PathVariable Long id,
@Valid @RequestBody Hotel hotel) {
try {
Hotel updatedHotel = hotelService.updateHotel(id, hotel);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(updatedHotel);
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}
// 호텔 삭제
@DeleteMapping("/{id}")
public ResponseEntity deleteHotel(@PathVariable Long id) {
try {
boolean deleted = hotelService.deleteHotel(id);
return deleted ? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}
}@ResponseBody와 HttpMessageConverter
@ResponseBody
- 설명: @ResponseBody는 메소드가 반환하는 객체를 JSON으로 마샬링하여 HTTP 응답 본문에 직접 쓰는 역할을 합니다. 이 어노테이션이 선언된 메소드는 객체를 JSON 형식으로 변환하여 클라이언트에게 전달합니다
- 적용 위치: 클래스 레벨에 선언하면 해당 클래스의 모든 메소드에 적용되며, 메소드 레벨에 선언하면 해당 메소드에만 적용됩니다
HttpMessageConverter
- 역할: 스프링에서는 HttpMessageConverter를 사용하여 요청과 응답의 데이터를 변환합니다. 예를 들어, MappingJackson2HttpMessageConverter는 JSON 데이터를 처리하는 기본 변환기입니다
- 작동 방식:
- 클라이언트가 JSON 형식의 데이터를 보내면, HttpMessageConverter가 이를 Java 객체로 변환합니다
- 서버가 Java 객체를 응답할 때, HttpMessageConverter가 이를 JSON 형식으로 변환하여 클라이언트에게 전송합니다
응답 메시지의 Content-Type
REST API는 JSON 형식으로 사용자에게 응답하며, 응답 메시지의 Content-Type은 application/json으로 설정됩니다. 이는 클라이언트가 응답을 올바르게 해석할 수 있도록 도와줍니다.
application.properties 설정
스프링 부트의 기본 설정 파일인 application.properties에서 필요한 기본 설정을 추가할 수 있습니다:
# 서버 포트 설정
server.port=8080
# 애플리케이션 이름
spring.application.name=hotel-management-api
# JSON 응답 포맷팅
spring.jackson.serialization.indent-output=true
spring.jackson.serialization.write-dates-as-timestamps=false
# 로깅 설정
logging.level.com.example=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# CORS 설정
management.endpoints.web.cors.allowed-origins=*
management.endpoints.web.cors.allowed-methods=GET,POST,PUT,DELETE실행 및 테스트
이제 스프링 부트 애플리케이션을 실행하고, Postman이나 cURL을 사용하여 REST API를 테스트할 수 있습니다.
모든 호텔 조회
GET 요청: http://localhost:8080/api/v1/hotels
curl -X GET "http://localhost:8080/api/v1/hotels" \
-H "Accept: application/json"호텔 추가
POST 요청: http://localhost:8080/api/v1/hotels
curl -X POST "http://localhost:8080/api/v1/hotels" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Grand Hotel",
"location": "Paris",
"description": "Luxury hotel in the heart of Paris",
"rating": 4.8
}'Body (JSON):
{
"name": "Hilton Hotel",
"location": "New York",
"description": "Premium business hotel",
"rating": 4.5
}특정 호텔 조회
GET 요청: http://localhost:8080/api/v1/hotels/1
curl -X GET "http://localhost:8080/api/v1/hotels/1" \
-H "Accept: application/json"호텔 정보 수정
PUT 요청: http://localhost:8080/api/v1/hotels/1
curl -X PUT "http://localhost:8080/api/v1/hotels/1" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Updated Hilton Hotel",
"location": "New York",
"description": "Updated description",
"rating": 4.7
}'호텔 삭제
DELETE 요청: http://localhost:8080/api/v1/hotels/1
curl -X DELETE "http://localhost:8080/api/v1/hotels/1"위치별 호텔 검색
GET 요청: http://localhost:8080/api/v1/hotels?location=New York
curl -X GET "http://localhost:8080/api/v1/hotels?location=New%20York" \
-H "Accept: application/json"결론
이 예제에서는 스프링 부트를 사용하여 간단한 REST API를 구축하는 방법을 살펴보았습니다. 각 스테레오타입 어노테이션을 활용하여 컨트롤러, 서비스, 리포지토리 계층을 설정하고, JSON 형식으로 응답하는 REST API를 구현했습니다.
핵심 포인트:
- @SpringBootApplication을 통한 자동 설정으로 빠른 개발 환경 구축
- 스테레오타입 어노테이션(@Controller, @Service, @Repository)을 활용한 명확한 계층 분리
- @RestController와 @ResponseBody를 통한 JSON 응답 자동화
- HttpMessageConverter의 자동 변환 기능으로 객체-JSON 간 매핑
- RESTful API 설계 원칙을 따른 명확한 엔드포인트 구조
이 구조를 기반으로 좀 더 복잡한 비즈니스 로직을 추가하여 확장할 수 있습니다. 데이터베이스 연동, 보안 설정, 예외 처리 등을 추가하여 실제 운영 환경에 적합한 API로 발전시킬 수 있습니다.
'IT기술 > MSA (with. springboot)' 카테고리의 다른 글
| Spring Boot DTO 패턴 완벽 가이드: MSA 환경에서의 효율적인 데이터 전송 객체 활용 (4) | 2025.07.11 |
|---|---|
| Spring Boot GET/DELETE API 매핑 완벽 가이드: MSA 환경에서의 호텔 관리 시스템 구현 (2) | 2025.07.09 |
| REST API 완벽 가이드: MSA 환경에서의 효율적인 서비스 간 통신 설계 (0) | 2025.07.09 |
| Spring Web MVC 프레임워크 완벽 가이드: MSA 환경에서의 웹 애플리케이션 구축 (0) | 2025.07.08 |
| Spring Boot DTO 패턴 완벽 가이드: MSA 환경에서의 효율적인 데이터 전송 객체 활용 (2) | 2025.07.07 |