✅ 스프링 부트 + JPA를 사용하여 회원 관리 API (등록, 조회, 삭제) 구현하기
@Table(name = "users")
: H2 시스템 내에 user
테이블이 존재하므로, 생성될 테이블의 이름을 users
로 지정
@JsonIgnore
: 조회 시 반환하지 않을 데이터 → 비밀번호와 같은 보안에 민감한 데이터
@Entity
@NoArgsConstructor
@Table(name = "users")
@Getter
public class User {
@Id @GeneratedValue
private Long id;
private String name;
private int age;
@JsonIgnore
private String password;
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
}
JpaRepository
사용 : 기본적인 CRUD 기능 제공public interface UserRepository extends JpaRepository<User, Long> {}
@RestController
= @Controller
+ @ResponseBody
: API 구현 시 사용
@Controller
: 뷰 페이지 반환@ResponseBody
: 반환되는 데이터를 JSON 등의 타입으로 변환하여 응답 메시지의 바디에 담아서 반환UserDTO
: 회원 목록 조회 시 회원 수 + 회원 목록
형태로 반환하기 위한 DTO
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserRepository repository;
@GetMapping
public UserDTO allUsers() {
List<User> users = repository.findAll();
return new UserDTO(users.size(), users);
}
@PostMapping
public ResponseEntity<User> save(@RequestBody User user) {
User saveUser = repository.save(user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@GetMapping("/{id}")
public User oneUser(@PathVariable Long id) {
Optional<User> optional = repository.findById(id);
if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");
return optional.get();
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
Optional<User> optional = repository.findById(id);
if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");
repository.deleteById(id);
}
@Data
@AllArgsConstructor
static class UserDTO {
private int cnt;
private List<User> users;
}
}
ex1) GET /users
: 회원 목록 조회
ex2) POST /users
: 회원 저장 → 상태코드 201 created
반환
ex3) GET /users/{id}
: 특정 회원 조회 → 존재하지 않는 회원일 경우 404 not found
반환
ex4) DELETE /user/{id}
: 특정 회원 삭제 → 존재하지 않는 회원일 경우 404 not found
반환
Hypermedia As The Engine Of Application State : 요청과 관련된 URI를 응답에 담아서 반환
ex) 회원 등록 시 회원 목록 + 특정 회원 조회 링크 제공, 특정 회원 조회 시 회원 목록 링크 제공 등 ...
- 리소스의 도입 : 서버가 가진 모든 리소스에 대한 고유한 URI 제공
- HTTP 동사 : HTTP Method를 통해 리소스에 수행하고자 하는 작업을 표현
- HATEOAS : 해당 자원에 대해 호출 가능한 API 정보를 상태에 반영하여 표현
ex) 회원 삭제 API 설계
Lv | Explain | Request | Response |
---|---|---|---|
1 | 리소스 표현 | POST /users/1/delete | JSON |
2 | 메서드를 통한 작업 표현 | DELETE /users/1 | JSON |
3 | 관련 API 링크 제공 | DELETE /users/1 | JSON + LINK (등록, 목록 조회 ... 등) |
✅ 특정 회원 조회 시 회원 목록, 회원 삭제 링크 전달하기
build.gradle
)implementation 'org.springframework.boot:spring-boot-starter-hateoas'
EntityModel
: 결과 데이터 + 관련 링크 반환
add()
: 링크 추가 ➕ withRel()
: 해당 링크에 대한 참조 (설명)WebMvcLinkBuilder
: 링크 생성 → 타겟 컨트롤러 내의 타켓 메서드에 매핑된 URI
linkTo()
: 컨트롤러 클래스를 가리키는 WebMvcLinkBuilder
객체 반환methodOn()
: 타겟 메서드의 가짜 메서드 콜이 들어있는 컨트롤러 프록시 클래스를 생성@GetMapping("/{id}")
public EntityModel<User> oneUser_HATEOAS(@PathVariable Long id) {
Optional<User> optional = repository.findById(id);
if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");
User user = optional.get();
EntityModel<User> entityModel = EntityModel.of(user);
WebMvcLinkBuilder allUser = linkTo(methodOn(this.getClass()).allUsers());
entityModel.add(allUser.withRel("all-user"));
return entityModel;
}
ex) GET /users/2
: 2번 회원 정보 + 회원 목록 API 링크 반환
개발한 REST API를 자동으로 문서화시키는 프레임워크 → API 목록 확인 및 테스트 가능
build.gradle
)
- Springfox (2020.07 이후로 업데이트 X → doc 사용 권장)
- Springdoc ⭐ (스프링 부트 3.x 이후 버전부터
springdoc-openapi-ui
사용 시 404 에러 발생)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
라이브러리 추가 후 /swagger-ui/index.html
에 접속하면 아래와 같은 페이지 확인 가능! (기본값 : 생성된 모든 API 컨트롤러 (@RestController
) + 객체 출력)
SwaggerConfig.java
@OpenAPIDefinition(info)
: API 명세서의 기본 정보 (제목, 설명, 버전 등)
GroupedOpenApi
: 여러 API 컨트롤러를 그룹화하여 표시
group
: 그룹 이름pathsToMatch
: 그룹에 속하는 경로 패턴@Configuration
@OpenAPIDefinition(
info = @Info(
title = "회원 관리 API 명세서",
description = "회원 관리 API 명세서",
version = "v1.0"
)
)
public class SwaggerConfig {
@Bean
public GroupedOpenApi userAPI() {
return GroupedOpenApi.builder()
.group("회원 관리")
.pathsToMatch("/users/**")
.build();
}
}
@Schema
: 모델(객체)에 대한 상세 정보@Entity
@NoArgsConstructor
@Table(name = "users")
@Getter
@Schema(description = "회원 정보를 관리하기 위한 도메인 객체")
public class User {
@Id @GeneratedValue
@Schema(title = "사용자 id", description = "자동 생성")
private Long id;
@Schema(title = "사용자 이름", description = "2 ~ 15글자 이내")
private String name;
@Schema(title = "사용자 나이", description = "5세 이상부터 가입 가능")
private int age;
@JsonIgnore
@Schema(title = "비밀번호", description = "8 ~ 20자 이내의 영소문자+숫자+특수기호(!~_=+@#) 조합")
private String password;
...
}
@Tag
: 클래스(컨트롤러)에 대한 상세 정보@Operation
: 특정 경로에 대한 작업(메서드)에 대한 상세 정보@Parameter
: 파라미터에 대한 상세 정보@ApiResponse
: 응답 상태코드에 대한 상세 정보@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
@Tag(name = "users", description = "회원 관리 컨트롤러")
public class UserController {
private final UserRepository repository;
@GetMapping
@Operation(summary = "회원 목록 API", description = "전체 회원 목록을 조회")
public UserDTO allUsers() {
List<User> users = repository.findAll();
return new UserDTO(users.size(), users);
}
@PostMapping
@Operation(summary = "회원 등록 API", description = "새로운 회원을 등록")
public ResponseEntity<User> save(@RequestBody User user) {
User saveUser = repository.save(user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@GetMapping("/{id}")
@Operation(summary = "회원 조회 API", description = "사용자 ID를 통해 특정 회원 조회")
@ApiResponses({
@ApiResponse(description = "OK", responseCode = "200"),
@ApiResponse(description = "NOT FOUND", responseCode = "404"),
@ApiResponse(description = "INTERNAL SERVER ERROR", responseCode = "500")
})
public EntityModel<User> oneUser_HATEOAS(@Parameter(description = "사용자 ID", required = true, example = "1")
@PathVariable Long id) {
Optional<User> optional = repository.findById(id);
if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");
User user = optional.get();
EntityModel<User> entityModel = EntityModel.of(user);
WebMvcLinkBuilder allUser = linkTo(methodOn(this.getClass()).allUsers());
entityModel.add(allUser.withRel("all-user"));
return entityModel;
}
@DeleteMapping("/{id}")
@Operation(summary = "회원 삭제 API", description = "사용자 ID를 통해 특정 회원 삭제")
public void deleteUser(@PathVariable Long id) {
Optional<User> optional = repository.findById(id);
if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");
repository.deleteById(id);
}
...
}
@Tag
+ @Operation
@Operation
+ @Parameter
@ApiResponses
🙇🏻♀️ 참고 1 : [개정판 2023-11-27] Spring Boot 3.x 를 이용한 RESTful Web Services 개발
🙇🏻♀️ 참고 2 : Hypermedia-driven REST API
🙇🏻♀️ 참고 3 : [SpringBoot] Swagger API 문서 자동화 간단 연동, 테스트하기
🙇🏻♀️ 참고 4 : [Spring Boot] Springdoc 라이브러리를 통한 Swagger 적용