티스토리 뷰
GET/POST/PUT/PATCH/DELETE를 어떻게 구현할까?
REST API를 공부하다 보면 GET, POST, PUT, PATCH, DELETE는 자주 보는데, 막상 Spring Boot에서 실제 CRUD 코드로 옮기려고 하면 헷갈리는 경우가 많습니다.
특히 초보 단계에서는 아래 질문이 자주 나옵니다.
GET과POST는 알겠는데PUT과PATCH는 정확히 뭐가 다를까?- CRUD API를 만들 때 메서드를 어떻게 나눠야 할까?
- 컨트롤러, 서비스, DTO는 어떤 식으로 연결해야 할까?
이번 글에서는 회원(User) 관리 예제를 기준으로, Spring Boot에서 GET/POST/PUT/PATCH/DELETE를 실제 CRUD로 어떻게 구현하는지 쉽게 정리해보겠습니다.
1. 먼저 CRUD와 HTTP 메서드를 연결해보자
CRUD는 데이터 처리의 4가지 기본 동작입니다.
- Create: 생성
- Read: 조회
- Update: 수정
- Delete: 삭제
이걸 HTTP 메서드에 연결하면 보통 이렇게 정리합니다.
| 동작 | 메서드 | URL 예시 | 설명 |
|---|---|---|---|
| Create | POST | `POST /users` | 새 사용자 생성 |
| Read | GET | `GET /users`, `GET /users/{id}` | 목록/단건 조회 |
| Update | PUT / PATCH | `PUT /users/{id}`, `PATCH /users/{id}` | 전체 수정 / 부분 수정 |
| Delete | DELETE | `DELETE /users/{id}` | 사용자 삭제 |
한 줄로 요약하면 아래처럼 기억하면 됩니다.
- GET = 조회
- POST = 생성
- PUT = 전체 수정
- PATCH = 부분 수정
- DELETE = 삭제
2. 예제 시나리오: User CRUD API 만들기
이번 글에서는 아래처럼 단순한 회원 관리 API를 만든다고 가정하겠습니다.
- 사용자 등록
- 사용자 전체 조회
- 사용자 단건 조회
- 사용자 전체 수정
- 사용자 일부 수정
- 사용자 삭제
실전에서는 인증, 검증, 예외처리, 페이징, 감사 로그까지 붙겠지만, 이번 글은 HTTP 메서드와 CRUD 흐름 이해가 목적이므로 핵심 구조 위주로 보겠습니다.
3. User 엔티티 예시
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private Integer age;
protected User() {
}
public User(String name, String email, Integer age) {
this.name = name;
this.email = email;
this.age = age;
}
public void update(String name, String email, Integer age) {
this.name = name;
this.email = email;
this.age = age;
}
public void updateName(String name) {
this.name = name;
}
public void updateEmail(String email) {
this.email = email;
}
public void updateAge(Integer age) {
this.age = age;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public Integer getAge() {
return age;
}
}
4. Repository 만들기
public interface UserRepository extends JpaRepository<User, Long> {
}
Spring Data JPA를 사용하면 기본 CRUD 메서드를 상당 부분 자동으로 사용할 수 있어서, 실무에서도 이 구조를 많이 씁니다.
5. Request / Response DTO 만들기
public class UserRequest {
private String name;
private String email;
private Integer age;
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public Integer getAge() {
return age;
}
}
public class UserResponse {
private Long id;
private String name;
private String email;
private Integer age;
public UserResponse(User user) {
this.id = user.getId();
this.name = user.getName();
this.email = user.getEmail();
this.age = user.getAge();
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public Integer getAge() {
return age;
}
}
6. Service에서 CRUD 로직 구현하기
@Service
@Transactional(readOnly = true)
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public Long createUser(UserRequest request) {
User user = new User(request.getName(), request.getEmail(), request.getAge());
User savedUser = userRepository.save(user);
return savedUser.getId();
}
public List<UserResponse> getUsers() {
return userRepository.findAll()
.stream()
.map(UserResponse::new)
.toList();
}
public UserResponse getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 사용자가 없습니다."));
return new UserResponse(user);
}
@Transactional
public void putUser(Long id, UserRequest request) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 사용자가 없습니다."));
user.update(request.getName(), request.getEmail(), request.getAge());
}
@Transactional
public void patchUser(Long id, UserRequest request) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 사용자가 없습니다."));
if (request.getName() != null) {
user.updateName(request.getName());
}
if (request.getEmail() != null) {
user.updateEmail(request.getEmail());
}
if (request.getAge() != null) {
user.updateAge(request.getAge());
}
}
@Transactional
public void deleteUser(Long id) {
if (!userRepository.existsById(id)) {
throw new IllegalArgumentException("해당 사용자가 없습니다.");
}
userRepository.deleteById(id);
}
}
이 코드에서 중요한 포인트는 아래입니다.
createUser()→ POST용 생성 로직getUsers(),getUser()→ GET용 조회 로직putUser()→ PUT용 전체 수정 로직patchUser()→ PATCH용 부분 수정 로직deleteUser()→ DELETE용 삭제 로직
7. Controller에서 HTTP 메서드 매핑하기
이제 컨트롤러에서 HTTP 메서드별로 연결해보겠습니다.
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<Void> createUser(@RequestBody UserRequest request) {
Long userId = userService.createUser(request);
URI location = URI.create("/users/" + userId);
return ResponseEntity.created(location).build();
}
@GetMapping
public ResponseEntity<List<UserResponse>> getUsers() {
return ResponseEntity.ok(userService.getUsers());
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUser(id));
}
@PutMapping("/{id}")
public ResponseEntity<Void> putUser(@PathVariable Long id,
@RequestBody UserRequest request) {
userService.putUser(id, request);
return ResponseEntity.ok().build();
}
@PatchMapping("/{id}")
public ResponseEntity<Void> patchUser(@PathVariable Long id,
@RequestBody UserRequest request) {
userService.patchUser(id, request);
return ResponseEntity.ok().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
이제 메서드별 의미가 명확해집니다.
POST /users→ 사용자 생성GET /users→ 사용자 목록 조회GET /users/{id}→ 사용자 단건 조회PUT /users/{id}→ 사용자 전체 수정PATCH /users/{id}→ 사용자 일부 수정DELETE /users/{id}→ 사용자 삭제
8. PUT과 PATCH는 어떻게 다를까?
초보자가 가장 헷갈리는 부분이 바로 여기입니다.
PUT
PUT은 전체 수정에 가깝습니다. 즉, 자원의 전체 상태를 새 값으로 바꾼다고 이해하면 쉽습니다.
예를 들어:
{
"name": "Kim",
"email": "kim@example.com",
"age": 30
}
이런 전체 데이터를 보내서 통째로 갱신하는 느낌입니다.
PATCH
PATCH는 부분 수정에 가깝습니다. 즉, 바꾸고 싶은 필드만 보내는 방식입니다.
예를 들어:
{
"email": "new-email@example.com"
}
이렇게 일부 값만 전달해서 수정할 수 있습니다.
한 번에 비교하면
|
PUT
전체 수정
|
PATCH
부분 수정
|
9. Postman이나 프론트엔드에서는 어떻게 호출할까?
예를 들어 사용자 생성 요청은 이렇게 보낼 수 있습니다.
POST /users
{
"name": "Kim",
"email": "kim@example.com",
"age": 30
}
PUT /users/1
{
"name": "Kim",
"email": "kim-new@example.com",
"age": 31
}
PATCH /users/1
{
"age": 31
}
이렇게 보면 Spring Boot 코드와 HTTP 요청이 어떻게 연결되는지 훨씬 잘 보입니다.
10. 실무에서 자주 보는 추가 포인트
10-1. 상태코드도 함께 맞춰야 한다
- 조회/수정 성공 →
200 OK - 잘못된 요청 →
400 Bad Request - 대상 없음 →
404 Not Found
10-2. 예외 처리는 별도로 관리하는 편이 좋다
@RestControllerAdvice를 두고 공통 예외 응답을 정리하는 경우가 많습니다.
그래야 컨트롤러/서비스 코드가 훨씬 깔끔해집니다.
10-3. 엔티티를 그대로 요청/응답에 쓰지 않는 편이 안전하다
DTO를 분리하면 API 스펙이 흔들리는 걸 줄일 수 있고, 프론트엔드와 협업할 때도 유리합니다.
11. 전체 흐름을 한 번에 보면
Spring Boot CRUD API의 흐름은 보통 이렇게 이어집니다.
- 클라이언트가 HTTP 요청을 보낸다
- Controller가 요청을 받는다
- Service가 비즈니스 로직을 처리한다
- Repository가 DB와 통신한다
- 결과를 ResponseEntity로 응답한다
즉, 단순히 메서드 어노테이션만 외우는 게 아니라, HTTP 요청이 애플리케이션 계층을 어떻게 흐르는지 함께 이해하는 것이 중요합니다.
12. 한 번에 정리하는 핵심 요약
GET은 조회POST는 생성PUT은 전체 수정PATCH는 부분 수정DELETE는 삭제- Spring Boot에서는
@GetMapping,@PostMapping,@PutMapping,@PatchMapping,@DeleteMapping으로 연결한다 - 이번 예제에서는 흐름 이해를 위해 Request DTO를
UserRequest하나로 통일했다 - 특히 PUT과 PATCH 차이, 상태코드 의미, Controller-Service-Repository 흐름은 꼭 함께 이해하는 것이 좋다
마무리
Spring Boot에서 CRUD API를 구현할 때 가장 중요한 건, HTTP 메서드 이름을 외우는 것이 아니라 각 메서드가 어떤 역할을 갖는지 정확히 이해하는 것입니다.
처음에는 GET/POST/PUT/PATCH/DELETE가 비슷해 보여도, 한 번 실제 CRUD 예제로 연결해보면 구조가 훨씬 선명해집니다.
'IT > Spring' 카테고리의 다른 글
| Spring Boot JWT 로그인 구현 방법 | Spring Security 설정부터 Access Token·Refresh Token 발급·검증까지 (0) | 2026.04.02 |
|---|---|
| [Spring] Spring이란 무엇인가? (0) | 2022.09.16 |
| [Spring] SpringBoot + JUnit5 단위테스트 (0) | 2022.09.05 |
| Spring Boot + MariaDB JPA 설정 방법 | Hibernate naming 오류와 테이블 대소문자 이슈까지 (0) | 2022.08.26 |
| Spring Boot + MariaDB 연동 방법 | Docker로 DB 띄우고 JDBC·MyBatis 설정까지 (0) | 2022.08.24 |

