2.3 서비스 개발 및 실습
- Logger 설정
- HTTP POST를 이용하는 Create REST API 개발
- HTTP GET을 이용하는 Retrieve REST API 개발
- HTTP UPDATE를 이용하는 Update REST API 개발
- HTTP DELETE를 이용하는 Delete REST API 개발
성해 보자. 2장에서 작성할 서비스는 생성Create, 검색 Retrieve, 수정Update, 삭제Delete 네가지 API다.
구현 과정은 퍼시스턴스 → 서비스→ 커트롤러 순으로 한다.
** 로그어노테이션
서비스 구현에 앞서 디버깅을 위한 로그 설정에 대해 설명하고자 한다. 로그를 어떻게 남길 수 있을까? 가장 간단한 방법으로는 System.out.println을 하는 방법이 있다. 이 방법은 유용하지만 기능이 제한적이다.
어떤 로그는 정보를 위한 것이고 어떤 로그는 디버깅을 위한 자세한 정보일 수 있다. 또 일부 로그는 심각한 에러를 알려주는 로그일 수도 있다. 이렇게 용도에 따라 로그를 크게 info, debug, warn, error로 나누고 이를 로그 레벨이라고 부른다. System.out.println으로 이를 모두 구현할 수도 있겠지만 우리에겐 이미 이런 기능을 제공하는 라이브러리가 존재한다. 바로 Slf4j 라이브러리다.
Slf4jSimple Logging Facade for Java는 로그계의 JPA 정도로 생각하면 된다. Slf4j를 사용하려면 구현부를연결해 줘야 한다. 짐작했겠지만 스프링이 이 작업을 알아서 해준다. 스프링은 기본적으로 Logback 로그 라이브러리를 사용한다. (Spring Boot Reference Documentation).
@Slf4j
@Service
public class TodoService {
// ... 메서드 생략
}
클래스 위에 @Slf4j 를 추가하자.
2.3.1 Create Todo 구현
** 퍼시스턴스 구현
@Repository
public interface TodoRepository extends JpaRepository<TodoEntity, String> {
@Query("SELECT * FROM TODO t WHERE t.userID = ?1")
List<TodoEntity> findByUserId(String userId);
}
https://strolrol.tistory.com/131
[Error] JPQL 문법 오류
1. 문제2025-02-17T20:33:21.451+09:00 WARN 2680 --- [demo] [main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException
strolrol.tistory.com
위 잘못된 코드,,,
** 서비스 구현
- 검증 Validation : 넘어온 엔티티가유효한지 검사하는 로직으로 이 부분은 코드가 더 커지면 TodoValidator.java로 분리시킬 수 있다.
- save() : 엔티티를 데이터베이스에 저장하고 로그를 남긴다.
- findByUserId() : 저장된 엔티티를 포함하는 새 리스트를 리턴한다.
@Slf4j
@Service
public class TodoService {
@Autowired
private TodoRepository repository;
public List<TodoEntity> create(final TodoEntity entity) {
// Validations
if(entity == null) {
log.warn("Entity cannot be null.");
throw new RuntimeException("Entity cannot be null.");
}
if(entity.getUserId() == null) {
log.warn("Entity cannot be null.");
throw new RuntimeException("Entity cannot be null.");
}
repository.save(entity);
log.info("Entity Id : {} is saved.", entity.getId());
return repository.findByUserId(entity.getUserId());
}
}
** 코드 리팩토링
검증 부분은 다른 메서드에서도 쓰일 예정이므로 private method로 리팩토링 한다.
@Slf4j
@Service
public class TodoService {
@Autowired
private TodoRepository repository;
public List<TodoEntity> create(final TodoEntity entity) {
// Validations
validate(entity);
repository.save(entity);
log.info("Entity Id : {} is saved.", entity.getId());
return repository.findByUserId(entity.getUserId());
}
// 리팩토링한 메소드
private void validate(final TodoEntity entity) {
// Validations
if (entity == null) {
log.warn("Entity cannot be null.");
throw new RuntimeException("Entity cannot be null.");
}
if (entity.getUserId() == null) {
log.warn("Entity cannot be null.");
throw new RuntimeException("Entity cannot be null.");
}
}
}
** 컨트롤러 구현
HTTP 응답을 반환할 때 비즈니스 로직을 캡슐화하거나 추가적인 정보를 함께 반환하려고 DTO를 사용한다. 따라서 컨트롤러는 사용자에게서 TodoDTO를 요청 바디로 넘겨받고 이를 TodoEntity로 변환해 저장해야 한다. 또 TodoService의 create()가 리턴하는 TodoEntity를 TodoDTO로 변환해 리턴해야 한다.
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class TodoDTO {
private String id;
private String title;
private boolean done;
public TodoDTO(final TodoEntity entity) {
this.id = entity.getId();
this.title = entity.getTitle();
this.done = entity.isDone();
}
public static TodoEntity toEntity(final TodoDTO dto) {
return TodoEntity.builder()
.id(dto.getId())
.title(dto.getTitle())
.done(dto.isDone())
.build();
}
}
@RestController
@RequestMapping("todo")
public class TodoController {
@Autowired
private TodoService service;
@PostMapping
public ResponseEntity<?> createTodo(@RequestBody TodoDTO dto) {
try {
String temporaryUserId = "temporary-user"; // temporary user id.
// (1) TodoEntity로 변환한다.
TodoEntity entity = TodoDTO.toEntity(dto);
// (2) id를 null로 초기화한다. 생성 당시에는 id가 없어야 하기 때문이다.
entity.setId(null);
// (3) 임시 사용자 아이디를 설정해 준다. 이 부분은 4장 인증과 인가에서 수정할 예정이다. 지금은 인증과 인가
// 기능이 없으므로 한 사용자(temporary-user)만 로그인 없이 사용할 수 있는 애플리케이션인 셈이다.
entity.setUserId(temporaryUserId);
// (4) 서비스를 이용해 Todo 엔티티를 생성한다.
List<TodoEntity> entities = service.create(entity);
// (5) 자바 스트림을 이용해 리턴된 엔티티 리스트를 TodoDTO 리스트로 변환한다.
List<TodoDTO> dtos = entities.stream().map(TodoDTO :: new).collect(Collectors.toList());
// (6) 변환된 TodoDTO 리스트를 이용해 ResponseDTO를 초기화한다.
ResponseDTO<TodoDTO> response = ResponseDTO.<TodoDTO>builder().data(dtos).build();
// (7) ResponseDTO를 리턴한다.
return ResponseEntity.ok().body(response);
} catch (Exception e) {
// (8) 혹시 예외가 있는 경우 dto 대신 error에 메시지를 넣어 리턴한다.
String error = e.getMessage();
ResponseDTO<TodoDTO> response = ResponseDTO .< TodoDTO>builder().error(error).build();
return ResponseEntity.badRequest().body(response);
}
}
}
** 추가 설명
entities.stream().map(TodoDTO :: new).collect(Collectors.toList());
TodoEntity 리스트를 TodoDTO 리스트로 변환하는 과정이다.
1. entities.stream()
- entities 리스트를 스트림(Stream)으로 변환한다.
- 스트림을 사용하면 리스트의 요소를 하나씩 가져와서 가공할 수 있다.
2. .map(TodoDTO :: new)
- map() 메서드는 스트림의 각 요소를 변환하는 역할을 한다.
- TodoDTO :: new는 생성자 참조(Constructor Reference) 표현식이다.
- 이것은 new TodoDTO(todoEntity)와 동일하다.
- 즉, TodoEntity 객체를 TodoDTO 객체로 변환하는 과정이다.
- TodoDTO 클래스에는 TodoEntity 객체를 매개변수로 받는 생성자가 있어야 한다.
public class TodoDTO {
private String title;
public TodoDTO(TodoEntity entity) {
this.title = entity.getTitle();
}
}
- 각 TodoEntity 객체가 TodoDTO 객체로 변환된다.
3. .collect(Collectors.toList())
- 변환된 TodoDTO 객체들을 리스트로 모은다.
- 최종 결과는 List<TodoDTO> 타입이 된다.
예제 코드
1. 기본 예제
import java.util.*;
import java.util.stream.Collectors;
class Todo {
String title;
public Todo(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
class TodoDTO {
String title;
public TodoDTO(Todo todo) {
this.title = todo.getTitle();
}
@Override
public String toString() {
return "TodoDTO{" + "title='" + title + '\'' + '}';
}
}
public class Main {
public static void main(String[] args) {
List<Todo> entities = Arrays.asList(new Todo("Study"), new Todo("Workout"), new Todo("Sleep"));
List<TodoDTO> dtoList = entities.stream()
.map(TodoDTO :: new)
.collect(Collectors.toList());
dtoList.forEach(System.out::println);
}
}
2. 실행 결과
TodoDTO{title='Study'}
TodoDTO{title='Workout'}
TodoDTO{title='Sleep'}
정리
- entities.stream() → entities 리스트를 스트림으로 변환
- .map(TodoDTO :: new) → Todo 객체를 TodoDTO 객체로 변환 (생성자 참조 사용)
- .collect(Collectors.toList()) → 변환된 TodoDTO 객체를 리스트로 수집
'React.js, 스프링 부트, AWS로 배우는 웹 개발 101' 카테고리의 다른 글
[TIL] 25/02/16, 서비스 개발 및 실습(2) (0) | 2025.02.18 |
---|---|
[Error] JPQL 문법 오류 (0) | 2025.02.17 |
[TIL] 25/01/29, 퍼시스턴스 레이어 : 스프링 데이터 JPA (0) | 2025.01.29 |
[TIL] 25/01/28, Service Layer : Business Logic (0) | 2025.01.29 |
[TIL] 25/01/27, REST API (0) | 2025.01.28 |