트러블 슈팅
406 Not Acceptable 에러
문제상황
Spring 프로젝트에서 클라이언트 요청에 대한 응답을 보낼 때 , 갑자기 406 에러가 발생했다.
'왜 응답을 보낼 수 없는 거지' property 문제인가 하고 한참을 고민했다.
해결방법
406 에러 발생의 주된 요인은 3가지라고 한다.
- jackson 라이브러리가 없기 때문인 경우. 다만 SpringBoot 프로젝트는 spring-boot-starter-web 에 기본적으로 jackson 라이브러리가 포함되어 있으니 패스.
- Accept에 사용된 MediaType이 설정 내에 없는 경우. 이것도 아닌거 같았다.
- Getter 메서드가 없어서. 나의 해당 사항이다.
@Getter
public class UserResponseDto {
private Long id;
}
이런식으로 DTO 클래스에 @Getter 를 추가해서 문제를 해결하였다.
.map() :스트림에서 데이터를 변환하는 강력한 도구
문제상황
Java Streams API에서 .map()을 처음 사용할 때, 이게 정확히 무슨 역할을 하는지 모르겠었다. 데이터를 변환하려고 했는데 Spring 에서 Map 형태로 데이터를 리턴하는 경우가 다양했기도 해서 인듯 하다.
해결방법
Map 메서드는 스트림의 각 요소를 다른 요소로 변환하는 데 사용한다.
1.기본적인 사용법을 숙지한다.
stream.map(element -> 변환식)
stream 은 스트림 객체를 나타내며, element 는 스트림의 각 요소를 가리키는 변수이다.
entitiy -> D T O 작업 수행
public class Main {
public static void main(String[] args) {
/** 엔티티 객체 리스트
* finAll()은 전체 맴버를 리스트로 반환하는 매서드, 엔티티 객체로 반환
*/
List<Entity> entities = memberRepository.finAll();
// DTO로 변환
List<DTO> dtos = entities.stream()
.map(entity -> new memberDTO(entity.getName, entity.getOld()))
.collect(Collectors.toList());
}
}
이렇게 변환되는 것을 구글링을 통해 알았고 최대한 적용해 보고자 했다.
mvc 흐름에서의 혼란.
문제상황
mvc(model-view-controller) 패턴은 기본적인 구조로 작은 프로젝트를 실행할 때 자주 쓰인다. 하지만, spring 을 접한지 얼마 안된 나에게 있어서는 이 패턴을 이용해서 왔다리 갔다리 하는것은 상당히 머리 아픈 일이였다.
해결방법
mvc 패턴의 각 부분이 어떻게 상호작용하는지 이해하는게 중요하다. 각 계층의 책임을 명확히 이해하고 문제를 추적해야 한다.
- Controller: 요청을 받고, 필요한 데이터를 추출해 서비스로 넘기는 역할.
- Service: 비즈니스 로직을 처리하는 곳.
- Repository: 데이터베이스와의 상호작용 담당.
여기서 또 DTO ENTITY 가 개입하니 머리가 터질 꺼 같았다.
다음은 일반적인 요청의 흐름이다.
- 클라이언트 요청: 클라이언트에서 API 요청을 보냄.
- 컨트롤러에서 요청 데이터 수신: 컨트롤러에서 클라이언트의 요청 데이터를 RequestDto로 받음.
- 서비스에서 처리: 컨트롤러가 받은 RequestDto를 서비스 계층으로 전달하고, 서비스에서는 DTO를 엔티티로 변환하여 데이터베이스에 저장.
- 엔티티를 다시 DTO로 변환: 서비스가 저장 또는 조회한 엔티티를 다시 ResponseDto로 변환.
- 클라이언트 응답: 변환된 ResponseDto를 클라이언트에게 응답으로 전달.
이런식으로 흐름에 대한 간략한 정리를 통해 전반적인 이해를 도왔다.
하다보니 보통 문제가
클라이언트 요청을 받는 컨트롤러 / 서비스에서 DTO를 엔티티로 변환할 때 / 서비스에서 엔티티를 DTO로 변환 후 응답할 때 / 클라이언트 응답으로 전달
이런식으로 전달 과정에서 골머리를 싸맸던거 같다.
PathVariable vs RequestParam
개념의 혼동
@PathVariable: URL 경로에서 값을 추출하는 데 사용. 주로 RESTful API에서 리소스를 식별할 때 사용.
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@RequestParam: URL의 쿼리스트링에서 파라미터를 추출할 때 사용. 옵션을 전달할 때
@GetMapping("/users")
public User getUserByName(@RequestParam String name) {
return userService.findByName(name);
}
즉, 경로에서 값을 추출할지, 쿼리 스트링에서 값을 가져올지 차이 인거 같다.
- 경로에서 값을 추출
http://example.com/users/123
123은 특정 사용자의 id 값임. 이 값이 경로의 일부로 사용됨.
즉, 경로 자체에서 어떤 값을 "추출"해서 서버에 넘기고 , 서버는 이 값을 활용해 데이터를 처리.
- 쿼리 스트링의 구조
사용자의 이름과 나이를 서버에 전달하는 쿼리 스트링 -> 필터링, 검색, 페이징 등 과 같이 정보를 서버에 전달할 때,
http://example.com/users?name=Jang&age=30
- ?name=Jang → name이라는 키에 Jang이라는 값을 전달
- &age=25 → age라는 키에 30라는 값을 전달
결론
MVC 흐름, PathVariable과 RequestParam, @Builder, 406 에러, .map()의 개념, 그리고 DTO -> Entity 변환 문제들을 다뤄보았다. 개발을 하다 보면 이런 문제들?(개념의 미숙지로인한)이 자주 발생할 수 있지만, 차근차근 디버깅하면서 문제를 해결해 나가는 것이 중요하다고 느꼈다. 아직,, 디버깅은 어렵고 힘든 길이다.