| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- jdbc template
- RunWith
- 통합 테스트
- 406 NOT_ACCEPTABLE
- 키움
- 가을야구
- 의존관계 자동 주입
- 스프링 데이터 JPA
- 의존성 주입
- Controller
- 테스트 코드
- 랜덤 포트
- 기본 생성자
- 빈 스코프
- jdbc
- JPA
- Java Reflection API
- restTemplate
- entity
- 스프링 IoC 컨테이너
- ResponseEntity
- 405 METHOD_NOT_ALLOWED
- 좋은 객체지향 설계 원칙
- testresttemplate
- 컴포넌트 스캔
- SpringBootTest
- 스프링 컨테이너
- 스프링
- 정적 컨텐츠
- Not Acceptable
- Today
- Total
코드네임 JY
[CRUD 연습] 406 Error in Spring 본문


스프링 공부 도중, '406 Not Acceptable' 이라는 에러 코드를 만나버렸다.. 😱😱
하지만 구글이 있다면? 절 대 로 무섭지 않지. 공부한 내용을 블로그에 정리해보도록 하겠다!
🍤 406 Not Acceptable 원인
ResponseEntity<MusicResponseDto> responseEntity
= restTemplate.getForEntity(url, MusicResponseDto.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(OK); // 406 NOT_ACCEPTABLE
에러 코드의 시발점(욕 아님 🥲🥲)은 바로 여기서부터 시작되었다.
responseEntity 변수에 getStatusCode( ) 메서드를 사용하면 Status Code를 확인할 수 있다고 이전 포스팅에서 공부했다!
그렇게 해서 값을 찍어봤는데, '406 Not Acceptable' 이 발생했다고 나온 것이다!
이제 근본적인 문제는 responseEntity 변수를 해결하는 것에 있다.
왜냐면? responseEntity가 제대로 된 Status Code(200 OK)와 body(url에 매핑된 데이터)를 가져오지 못 했기 때문이다.
그렇다면 실제로 ResponseEntity가 어떻게 동작하는지 살펴볼 필요가 있다. (이거 진짜 로그 다 찍어보며 엄청나게 공부했다 ㅠ)
🍣 통합 테스트 진행 과정 (로그 찍어서 연구)
구글링을 해보면서 얻은 답은, 현재 MusicAPIController에서 DTO로 사용하는 객체에 Getter가 없는 것이 원인이라고 한다.
아래 두 코드는 각각 Controller, Service 코드이다.
@RestController
public class MusicAPIController {
private final MusicService musicService;
@GetMapping("/api/v1/music/{id}")
public MusicResponseDto findById(@PathVariable Long id) {
MusicResponseDto responseDto = musicService.findById(id);
return responseDto;
}
}
@Service
public class MusicService {
private final MusicRepository musicRepository;
public MusicResponseDto findById(Long id) {
Optional<Music> result = musicRepository.findById(id);
Music entity = new Music();
if (result.isPresent()) { // Optional 처리
entity = result.get();
} else {
System.out.println("적절한 예외 처리 필요!");
}
return new MusicResponseDto(entity);
}
}
그리고 문제의 원인이 있는 DTO 코드. '406 Not Acceptable' 에러는 Getter가 없어서 발생한 것이다!
public class MusicResponseDto {
private Long id;
private String title;
private String artist;
private String album;
private String lyrics;
public MusicResponseDto() {}
public MusicResponseDto(Music entity) {
this.id = entity.getId();
this.title = entity.getTitle();
this.artist = entity.getArtist();
this.album = entity.getAlbum();
this.lyrics = entity.getLyrics();
}
// Getter 없어서 발생한 문제!
}
그리고 위 3개의 코드가 유기적으로 연결되어 잘 동작하는지 알아볼 수 있는 테스트 코드이다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MusicAPIControllerTest {
@LocalServerPort private int port;
@Autowired private MusicRepository musicRepository;
@Autowired private TestRestTemplate restTemplate;
@Test
void 조회() {
// given
Music music = new Music("Loco", "ITZY", "CRAZY IN LOVE", "I'm gettin loco");
musicRepository.save(music);
String url = "http://localhost:" + port + "/api/v1/music/" + music.getId();
// when
ResponseEntity<Music> responseEntity = restTemplate.getForEntity(url, Music.class);
MusicResponseDto responseDto = new MusicResponseDto(responseEntity.getBody());
// then
assertThat(responseDto.getTitle()).isEqualTo(music.getTitle());
assertThat(responseEntity.getStatusCode()).isEqualTo(OK);
}
}
이제부터 테스트 코드를 중심으로, Controller, Service, DTO가 어떻게 연결되어 있는지 설명하도록 하겠다.
그러면서 DTO에 왜 Getter가 필요했는지 이유를 찾아보도록 하겠다!


위 과정(Step 1~6)을 문제 없이 진행했다면, 컨트롤러에서 사용되는 DTO에 Entity 객체가 매핑되어야 한다.

DTO 객체의 생성자 부분에 로그를 찍어서 확인해보면, DTO에 Entity 객체의 데이터가 잘 매핑된 것을 확인할 수 있다.
음? 그러면 406 에러가 뜬 이유는 뭐지? DTO에 데이터를 잘 받아왔으니까 200 OK가 떠야하는거 아닌가?
문제는 바로 Controller 에서 DTO를 리턴하는 부분에 있다.
return responseDto; 라는 코드는 매우 단순해서 그냥 넘어갈 만 하지만, 그렇지 않았다.. 😭😭
public class MusicResponseDto {
private Long id;
private String title;
private String artist;
private String album;
private String lyrics;
public MusicResponseDto() {}
public MusicResponseDto(Music entity) {
System.out.println("MusicResponseDto.MusicResponseDto Constructor Call");
this.id = entity.getId();
this.title = entity.getTitle();
this.artist = entity.getArtist();
this.album = entity.getAlbum();
this.lyrics = entity.getLyrics();
System.out.println("MusicResponseDto.MusicResponseDto Constructor End");
}
// Getter 추가
public Long getId() {
System.out.println("MusicResponseDto.getId");
return id;
}
public String getTitle() {
System.out.println("MusicResponseDto.getTitle");
return title;
}
public String getArtist() {
System.out.println("MusicResponseDto.getArtist");
return artist;
}
public String getAlbum() {
System.out.println("MusicResponseDto.getAlbum");
return album;
}
public String getLyrics() {
System.out.println("MusicResponseDto.getLyrics");
return lyrics;
}
}
DTO에 Getter가 없어서 발생한 문제이므로, Getter를 만들고 각각 로그를 적어두어서 Getter가 호출되는지 확인하였다.
@RestController
public class MusicAPIController {
private final MusicService musicService;
@GetMapping("/api/v1/music/{id}")
public MusicResponseDto findById(@PathVariable Long id) {
MusicResponseDto responseDto = musicService.findById(id);
System.out.println("Come back to Controller");
return responseDto;
}
}
그리고 컨트롤러에서 DTO를 리턴하기 전, 로그 하나를 찍어두고 테스트 코드를 실행해보았더니..

이게 뭐람? DTO 객체의 생성자를 통해 Entity 데이터가 매핑되는 것은 확인했는데,
컨트롤러에 다시 돌아온 후 responseDto를 리턴할 때 DTO 객체의 Getter가 실행되는 것을 확인할 수 있다..!! 🔥🔥
결국 DTO에 Getter가 필요한 이유는, 컨트롤러에서 DTO 객체의 값을 리턴할 때 필요했던 것이었다!
그렇다면 Status Code가 406(서버가 목록과 일치하는 응답을 생성할 수 없음)으로 발생했던 이유는 무엇일까?
컨트롤러에서 DTO 객체를 리턴해줄 때, DTO 객체에 Getter가 없었기 때문에 값을 가져올 수 없었고,
그래서 제대로 된 응답을 생성할 수 없었던 것이다!
결론 : Controller에서 DTO 객체를 리턴해줄 때, DTO 객체의 값을 가져오기 위해서는 Getter가 반드시 필요하다!
사실 이 문제는 롬복을 써서 처음부터 @Getter 어노테이션을 사용했다면, 간단하게 해결할 수 있는 문제였을 것이다.
하지만 롬복은 편의 기능이라서, 공부하는데 큰 도움이 될 것이라 생각하지 않았고, 일단 롬복 없이 전체 프로젝트를 구현한다.
그리고 전체적으로 기능을 완성하면, 추후에 코드를 리팩토링하는 과정에서 롬복을 추가할 예정이다!
'백엔드 공부' 카테고리의 다른 글
| [CRUD 연습] CRUD 기능 구현 (0) | 2023.01.13 |
|---|---|
| [CRUD 연습] 405 Error in Spring (0) | 2023.01.12 |
| [CRUD 연습] RestTemplate으로 GET, POST (0) | 2023.01.11 |
| [CRUD 연습] 엔티티티티 프레자일 (0) | 2023.01.10 |
| [CRUD 연습] 컨트롤러 테스트 가이드 (0) | 2023.01.10 |