728x90

오늘은 살짝 신기한 에러 상황을 만나게 되어 가져와 보았습니다.

서버에서 RequestBody로 데이터를 받아 처리를 하거나 아니면 시스템상 타입을 추론하기 어려운 상황에서 발생할 수 있는 오류입니다.

 

문제의 해결은 문제를 이해하는 것부터 시작한다는 말이 있습니다.

그 말대로 우리는 오류를 직접 발생시켜 봄으로써 해당 오류를 더욱 자세하게 알아보도록 하겠습니다.

1. 데이터를 받을 객체 정의

시스템상 데이터 타입을 추적하지 못하는 케이스를 만들기 위해서는 데이터를 담을 수 있는 특정 타입 즉 클래스가 필요하기 때문에 하나 생성해 주도록 합니다.

public class TestData {

    private String name;
    private String description;
    private TestField field;
}

 

 

2. 테스트

public static void main(String[] args) throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    String data = "{\"name\": \"홍길동\", \"description\": \"데이터 파싱 테스트입니다.\", \"field\": {\"data\": \"파싱이 될까요?\"}}";
    Object dd = objectMapper.readValue(data, Object.class);
    TestData field = ((TestData)dd);
}

위와 같이 작성하여 테스트를 진행해 보시면 바로 아래와 같은 오류가 발생하는 것을 확인하실 수 있습니다.

 

문제 발생원인

여기서 왜 오류가 발생했는지 그리고 사용한 적도 없는 LinkedHashMap이 갑자기 왜 나온 것인이 의아할 수 있습니다.

이는 직렬화와 역직렬화를 도와주는 Jackson에 의해 발생하는 오류입니다.

 

역직렬화를 할 경우 Jackson은 데이터 타입을 받아 해당 데이터 타입으로 역직렬화를 진행하게 되는데 Jackson이 객체의 타입이 불명확하게 인지할 경우 기본적으로 LinkedHashMap을 사용하여 객체로 만들어 주게 됩니다. 

실제로 위 코드에서 dd 로 정의한 객체의 타입을 찍어보면 위와 같이 LinkedHashMap으로 정의되어 있는 것을 확인할 수 있습니다.

이러한 상황은 전부 Jackson이 객체 타입을 추론하지 못해 발생한 오류 상황인 것입니다.

해결방안

해결방법은 생각보다 간단합니다.

그저 데이터를 역직렬화할 때 명확한 데이터 타입을 명시해 주면 되는 것입니다.

 

위에 작성한 코드로만 본다면  아래와 같이 타입을 Object처럼 포괄적인 의미가 아닌 명확한 객체 타입을 정해주면 해당 오류가 발생하지 않습니다.

{
	...
    TestData dd = objectMapper.readValue(data, TestData.class);
    ...
}

 

다른 에러 발생 케이스

하지만 위에 작성된 케이스는 오류를 일부러 발생하기 위해 간단하게 테스트한 경우이고 실제 업무에서는 어떤 상황일 때 발생할까요?

아무래도 실제 사용하는 케이스가 필요하다 생각합니다.

 

그리하여 일반적인 API호출하는 것처럼 Get API를 하나 만들어 주도록 하겠습니다.

@RestController
@RequestMapping("/error")
public class ErrorController {

    @Autowired
    private ObjectMapper objectMapper;

    @GetMapping
    public QueryData<TestData> test() throws JsonProcessingException {
        String data = "{\"name\": \"홍길동\", \"description\": \"데이터 파싱 테스트입니다.\", \"field\": {\"data\": \"파싱이 될까요?\"}}";
        QueryData<TestData> queryData = new QueryData<>();
        queryData.setResponse(objectMapper.readValue(data, TestData.class));
        return queryData;
    }
}

일반적인 값을 반환해주는  API입니다.

public class QueryData<T> {
    private T response;
}

Response를 Wrapping할 때 사용한 클래스입니다.

단순하게 response 를 담기 위해 만들었습니다.

 

@SpringBootTest
@AutoConfigureMockMvc
public class ErrorControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void dataParsingTest() throws Exception {
        mockMvc.perform(get("/error").contentType(MediaType.APPLICATION_JSON))
                .andDo((result) -> {
                    QueryData<TestData> queryData = objectMapper.readValue(result.getResponse().getContentAsString(Charset.defaultCharset()), QueryData.class);
                    //에러 발생 부분
                    TestData testData = queryData.getResponse();
                })
                .andExpect(status().isOk());
    }
}

테스트 코드입니다.

SpringBoot환경에서 Mock을 사용하여 테스트 진행하였습니다.

 

위 테스트 코드를 실행해 보시면 response로 받은 데이터를 역직렬화하여 사용하였으나 제너릭에 명시한 타입으로 QueryData의 response가 TestData가 아닌 LinkedHashMap으로 타입이 정의되어 캐스팅할 때 에러가 발생하는 것을 확인할 수 있습니다.

 

제가 경험했던 오류 케이스도 이와 같았으며, Response Data가 한번 객체로 감싸져 있으며, 타입이 명확하지 않아 Object로 인지되어 역직렬화가 된경우 데이터를 꺼내 사용할 때 캐스팅이 이뤄지게 되는데 이때 에러가 발생합니다.

 

또다른 해결 책입니다.

ObjectMapper의 convertValue를 사용하여 

TestData testData = objectMapper.convertValue(queryData.getResponse(), TestData.class);

위와 같이 코드를 수정할 경우 오류 없이 정상적으로 데이터를 꺼내 사용할 수 있으니 편한 방법으로 사용하시면 될 것 같습니다.

728x90

'Error' 카테고리의 다른 글

[Mysql] max_allowed_packet  (0) 2024.04.08
728x90

데이터 마이그레이션 후 갑자기 목록 조회가 안되는 현상이 발생했다.

log를 확인해보니

라는 에러가 발생했다.

???

누구세요

에러 로그를 읽어보면 조회를 하는데 조회할때 할당된 패킷은 4,194,304byte인데 11,594,289byte의 패킷이 조회되서 에러 띄웠다고 한다.

그럼 Mysql의 패킷 단위를 변경하면 되는 것이 아닐까??

찾아보니 max_allowed_paket이라는 설정 값을 변경해주면 된다고 한다.

그럼 max_allowd_paket은 뭔가??
말 그대로 데이터 베이스 서버에서 한번에 전송되는 최대 데이터 패킷 크기를 결정하는 설정이라고 한다.

즉 데이터를 조회 혹은 등록 할 때 주고 받을 수 있는 데이터의 양이라고 보면 편할 것 같다.

우선 현재 max_allowd_paket 이 얼마로 설정되어 있는지 확인해보자.

show variables where Variable_name = 'max_allowed_packet';

확인 후 본인이 늘리고 싶을 만큼 늘려보자 ( 주의 패킷이 주고받는 데이터 양을 의미하는데 생각없이 너무 늘려버리면 그것은 메모리 누수로 이어질 수 있을 거 같다. )

SET GLOBAL max_allowed_packet =12000000

늘리고 난 이후 Mysql의 설정을 적용해 주도록 하자

FLUSH PRIVILEGES

이후 확인을 해보면 패킷 최대 용량이 변경 된 것을 확인할 수 있을 것이다.

위의 방법은 command로 적용하는 방법이였고,
설정 파일을 수정하려면 mysql 설정파일 my.cnf또는 my.init에서

max_allowed_packet=16M

로 설정을 추가 혹은 변경해주면 될 것이다.

오늘 하루도 화이팅하자...야근 하이....

728x90

'Error' 카테고리의 다른 글

[Error] java.util.LinkedHashMap cannot be cast to Class  (0) 2024.04.16

+ Recent posts