반응형
Notice
Recent Posts
Recent Comments
Link
250x250
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Linux
- 일본첫여행
- 전세대출
- 문래맛집
- 허그전세대출
- Docker
- 부천데이트
- 제주도가성비여행
- restapi
- 비아인프라임교토에키
- 부천데이트맛집
- 부천맛집
- 오사카여행
- 결혼준비예상비용
- 혼공컴운
- 교토여행
- 혼공컴운스터디
- 제주도여행
- 강서구웨딩홀추천
- 컴퓨터구조
- 교토숙소
- 일본여행
- windows10
- WSL2
- 혼공컴운자습문제
- 전세사기
- 부천술집
- 우메다홀릭호텔
- 제주여행비용정리
- 부천역맛집
Archives
- Today
- Total
sugar.kim
Spring boot Rest API (GET/POST/PUT/DELETE) 본문
728x90
반응형
Spring Boot에서 REST API를 설계할 때 흔히 사용하는 Controller‑Service‑Repository 구조(MVC 패턴 변형)에서 “Model”은 크게 두 가지 관점으로 이해 할 수 있다.
- 도메인 모델(Domain Model) / 엔티티(Entity)
- 데이터 전송 객체(Data Transfer Object, DTO) ⬅️ 이거 관련해서만 해당 내용 정리
아래에서 각각 어떻게 쓰이고, Controller에서는 어떻게 활용되는지 예시와 함꼐 살펴보자.
1. 도메인 모델 (Entity)
- 역할: 데이터베이스 테이블 구조를 Java 클래스로 표현
- 어노테이션: @Entity, @Table, @Id 등 JPA 표준 어노테이션 사용
- 예시: 사용자(User) 정보를 담는 엔티티
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String username;
@Column(nullable = false)
private String email;
// Getter / Setter, 기본 생성자
}
- Repository 레이어(@Repository)에서 이 엔티티를 CRUD 대상으로 사용
- DB 스키마 변경 시 모델만 수정하면 된다.
2. DTO (Data Transfer Object) ⬅️ 이거 관련
- 역할: 네트워크(HTTP) 레이어와 도메인 모델 사이에서 주고받을 데이터 형태를 정의
- 장점
- 엔티티에 민감 정보(비밀번호, 내부 ID 등)가 노출되는 걸 방지
- 입력값 검증(@Valid + @NotNull 등)과 API 스펙 분리가 쉬워짐
- API 버전 관리, 응답 필드 제어 등에 유리
// 요청 바디로 받을 때
public class UserCreateRequest {
@NotBlank
private String username;
@Email @NotBlank
private String email;
// Getter / Setter
}
// 응답으로 반환할 때
public class UserResponse {
private Long id;
private String username;
private String email;
// 생성자, Getter
}
3. Controller 레이어 (@RestController)⬅️ 이거 관련
- 역할: HTTP 요청을 받고, Service를 호출한 뒤, DTO로 응답 반환
- 어노테이션
- @RestController : @Controller + @ResponseBody
- @RequestMapping / @GetMapping, @PostMapping 등
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
// 생성자 주입
public UserController(UserService userService) {
this.userService = userService;
}
// 1) 회원 생성
@PostMapping
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody UserCreateRequest req) {
User newUser = userService.createUser(req);
UserResponse res = new UserResponse(newUser.getId(),
newUser.getUsername(),
newUser.getEmail());
return ResponseEntity
.status(HttpStatus.CREATED)
.body(res);
}
// 2) 회원 조회
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(
@PathVariable Long id) {
User user = userService.findById(id);
UserResponse res = new UserResponse(user.getId(),
user.getUsername(),
user.getEmail());
return ResponseEntity.ok(res);
}
// 기타 업데이트, 삭제 API…
}
주요 포인트
- 입력 검증
- @Valid + DTO에 정의한 제약 애너테이션(@NotBlank, @Email 등)
- @ExceptionHandler 혹은 전역 @ControllerAdvice로 에러 응답 핸들링
- Service 호출
- 비즈니스 로직은 Controller가 아니라 Service 레이어에 위임
- Controller는 순수하게 “요청 → DTO 변환 → Service 호출 → DTO 응답” 흐름만 관리
- 응답 상태 코드
- 생성 시 201 CREATED, 조회 시 200 OK, 삭제 시 204 NO CONTENT 등 REST 표준에 맞춰 설정
4. Service & Repository
- Service (@Service)
- 트랜잭션 관리(@Transactional)
- 도메인 모델 생성·조회·수정·삭제 비즈니스 로직
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) {
this.repo = repo;
}
@Transactional
public User createUser(UserCreateRequest req) {
User user = new User();
user.setUsername(req.getUsername());
user.setEmail(req.getEmail());
return repo.save(user);
}
public User findById(Long id) {
return repo.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
}
}
- Repository (@Repository, Spring Data JPA)
- 기본 CRUD 메서드 제공: save(), findById(), deleteById() 등
public interface UserRepository extends JpaRepository<User, Long> {
// 필요 시 추가 쿼리 메서드 정의 가능
Optional<User> findByUsername(String username);
}
전체 요청 흐름
HTTP 요청 → DispatcherServlet
→ HandlerMapping: Controller 클래스와 메서드 결정
→ UserController
→ DTO 바인딩 & 검증
→ UserService 호출
→ UserRepository (DB) 작업
← 결과 리턴
→ DTO 변환
← ResponseEntity 반환 → HTTP 응답
결론
- Model 은 크게 엔티티(Entity) 와 DTO 로 나뉜다.
- Controller 에서는 DTO 를 통해 요청/응답을 처리하고, Service 에서는 Entity 를 다루는 것이 모범 사례.
- 이렇게 계층을 분리하면 유지보수성, 보안, 테스트 용이성이 모두 향상.
추가로 ModelMapper, MapStruct 같은 라이브러리를 사용해 DTO ↔ Entity 변환을 자동화할 수도 있다.
‼️Controller 에서는 DTO 를 통해 요청/응답을 처리
📌 Spring Boot REST API 관련 어노테이션 정리 표
어노테이션 및 예시 | 설명 | 주요 특징 / 비고 |
@Slf4j | 롬복(Lombok)에서 제공하는 로깅 어노테이션. log.info(), log.error() 등 사용 가능 | System.out.println() 대신 사용해야 하는 이유: ✔ 로그 레벨 관리 가능 ✔ 성능과 모니터링 최적화 ✔ 운영 환경에서 로그 파일 기록 및 분석 가능 |
@RestController | 컨트롤러 클래스임을 선언 + 모든 메서드가 @ResponseBody 자동 포함 | 주로 REST API 컨트롤러에 사용. HTML 뷰가 아닌 JSON 형태의 응답을 반환함 |
@RequestMapping("/api") | 공통 URL Prefix 설정 | base URL: http://localhost:8080/api |
@GetMapping("/hello") | GET 방식으로 요청 받는 메서드 매핑 | 예: GET http://localhost:8080/api/hello |
@GetMapping("/echo/{message}") | URL 경로의 일부를 변수로 받을 때 사용 | @PathVariable String message로 바인딩 예: GET http://localhost:8080/api/echo/hi |
@PathVariable(name="message") String msg | PathVariable 명이 변수명과 다를 때 매핑 | http://.../echo/hello → msg = "hello" |
@GetMapping("/book") @RequestParam String category 등 |
쿼리 파라미터를 1:1로 받아올 때 사용 | 예: GET /book?category=IT&issuedYear=2023 등 |
@RequestParam(name="issued-month") String issuedMonth | 쿼리파라미터 이름과 변수명이 다를 때 명시적으로 이름 설정 | ?issued-month=01 → issuedMonth = "01" |
BookQueryParam bookQueryParam | 쿼리파라미터가 여러 개인 경우 DTO로 한번에 바인딩 가능 | DTO 클래스는 레퍼런스 타입(String 등) 으로 구성해야 함. 📌 이유: 기본형(int, boolean)은 null 처리가 안돼서 값이 누락되면 예외 발생 |
DTO 어노테이션 @Data, @AllArgsConstructor, @NoArgsConstructor |
Lombok에서 DTO 클래스의 필수 메서드 자동 생성 | @Data: Getter, Setter, toString 등 @AllArgsConstructor: 전체 필드 있는 생성자 @NoArgsConstructor: 파라미터 없는 기본 생성자 |
@PostMapping("/post") @RequestBody |
JSON 형태의 요청 본문(body)을 자바 객체로 바인딩 | 주로 DTO 사용: @RequestBody UserDto dto |
@PutMapping("/put") @RequestBody |
PUT 방식 (수정 요청) + 바디로 데이터 전달 | PUT도 JSON body → DTO 매핑 |
@DeleteMapping({"/user/{userName}/delete", "/user/{userName}/del"}) | 2개 이상의 경로를 하나의 메서드로 처리 가능 | @PathVariable String userName으로 값 매핑 가능 |
✅ DTO는 왜 레퍼런스 타입(String 등)으로 작성해야 하나?
구분 | 설명 |
기본형 사용 예시 | private int age; |
문제점 | 쿼리 파라미터에서 age가 안 들어올 경우 → int는 null이 불가능 → 400 오류 발생 |
레퍼런스형 사용 예시 | private Integer age;, private String issuedYear; |
장점 | 누락되면 null로 들어와도 예외 발생 안 함 → 검증 단계에서 처리 가능 → 안정적 API 처리 가능 |
결론:
✅ DTO에서는 int → Integer, boolean → Boolean, double → Double 처럼 레퍼런스 타입으로 선언해야 합니다.
✅ System.out.println() 대신 log.info()를 써야 하는 이유
구분 | System.out.println() | og.info() <from @Slf4j> |
용도 | 단순 출력 | 로그 수준별 출력 및 로깅 시스템 연동 |
로그 레벨 | 없음 | trace, debug, info, warn, error 등 |
성능 | 느림, 비효율적 | 로그 출력 여부에 따라 내부적으로 출력 여부 결정되어 효율적 |
운영 환경 | 로그파일 저장 불가 | 로그 파일로 저장 가능, 로그 관리 시스템과 연동 가능 |
필터링 | 안 됨 | 로그레벨, 태그, 모듈별 필터링 가능 |
결론:
운영 서비스에서는 반드시 System.out 대신 log.info(), log.error() 등으로 로그를 남겨야 하며,
이때 @Slf4j를 클래스에 붙이면 별도 선언 없이 바로 log 객체를 사용할 수 있어 편리합니다.
📌 Jackson JSON 네이밍 전략 관련 어노테이션
어노테이션 | 설명 | 예시 및 비고 |
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | JSON 요청/응답의 필드명을 **스네이크 케이스(snake_case)**로 매핑 | ✅ 클래스 단위로 사용 ✅ DTO 필드가 issuedYear일 경우 → JSON 키는 issued_year로 매핑됨 예) {"issued_year": "2023"} → dto.getIssuedYear() 로 접근 가능 |
✅ 언제 사용하는가?
- 클라이언트(JSON)가 **스네이크 케이스(snake_case)**를 사용하는 경우
- 서버 자바 코드는 **카멜 케이스(camelCase)**로 유지하면서 매핑을 맞추고 싶을 때
✅ 사용 위치 예시
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class BookQueryParam {
private String issuedYear; // → JSON: issued_year
private String issuedMonth; // → JSON: issued_month
private String issuedDay; // → JSON: issued_day
}
728x90
반응형
'DEV > Java + Spring Boot' 카테고리의 다른 글
REST API란? (+상세 설명 까지) (0) | 2025.05.19 |
---|
Comments