반응형


최근에 특정 모듈을 분석해서 다시 만들어야 하는 작업을 진행하였다.


해당 모듈이 약간 성능에 민감한 모듈이다 보니 가능한 한 빠르게 동작하도록 하였어야 했다.


그러다가 ArrayList.contains() 메서드로 된 부분이 있어 HashSet.continas으로 수정해 성능이 조금 높아 진 것을 확인했는데


기본적인 개념으로만 HashSet이 내부적으로 HashMap을 구현하고 있어서 빠를 것 같다는 생각을 했었는데

좀 더 자세히 알고 싶어 포스팅 하게 되었다.


[ HashSet.Contains() ]

내부적으로 HashSet은 HashMap Instance를 구현하고 있고 contains() 메서드를 호출하게되면 HashMap.containsKey(object)가 호출된다고 보면 된다. 자세한 설명은 밑을 참고하자.

Internally, the HashSet implementation is based on a HashMap instance. The contains() method calls HashMap.containsKey(object).

Here, it’s checking whether the object is in the internal map or not. The internal map stores data inside of the Nodes, known as buckets. Each bucket corresponds to a hash code generated with hashCode() method. So contains() is actually using hashCode() method to find the object’s location.

Now let’s determine the lookup time complexity. Before moving ahead, make sure you are familiar with Big-O notation.

On average, the contains() of HashSet runs in O(1) time. Getting the object’s bucket location is a constant time operation. Taking into account possible collisions, the lookup time may rise to log(n) because the internal bucket structure is a TreeMap.

This is an improvement from Java 7 which used a LinkedList for the internal bucket structure. In general, hash code collisions are rare. So we can consider the elements lookup complexity as O(1).



[ ArrayList.contains() ] 

ArrayList는 해당 값이 list에 있는지 판단하기 위해 내부적으로 indexOf(object) 메서드를 사용한다. indexOf(object) 메서드는 array 전체를 반복해서 돌고 각각의 element와 비교를 진행한다. 자세한 설명은 밑을 참고하자.

Internally, ArrayList uses the indexOf(object) method to check if the object is in the list. The indexOf(object)method iterates the entire array and compares each element with the equals(object) method.

Getting back to complexity analysis, the ArrayList.contains() method requires O(n) time. So the time we spend to find a specific object here depends on the number of items we have in the array. 



설명을 봤듯이 HashSet은 내부적으로 HashMap을 사용하고 ArrayList는 contains 메서드 내부에서 모든 element들을 돌며 비교작업을 진행하기 때문에 당연히 HashSet의 contains메서드가 빠를 수 밖에 없었던 것이다.


[ 테스트 코드 ]

1. 100만개의 String을 생성하여 HashSet, ArrayList를 만든다.

2. 500번 반복문을 돌며 Set과 List의 contains메서드를 사용해 확인한다.


HashSet Test

@Test
public void testHashSetContainsPerformance() {
Set<String> testSet = new HashSet<>();
String baseStr = "hellotheworld";
Random generator = new Random();

long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
testSet.add(baseStr + i);
}
long end = System.currentTimeMillis();
System.out.println("Setup Performance : " + (end - start));

long start2 = System.currentTimeMillis();
int matchingCount = 0;
for (int j =0; j < 500; j++) {
if (testSet.contains(baseStr + generator.nextInt(5000))) {
matchingCount++;
}
}
long end2 = System.currentTimeMillis();
System.out.println("HashSet Contains Performance : " + (end2 - start2));
}


ArrayList Test

@Test
public void testArrayListContainsPerformance() {
List<String> testList = new ArrayList<>();
String baseStr = "hellotheworld";
Random generator = new Random();

long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
testList.add(baseStr + i);
}
long end = System.currentTimeMillis();
System.out.println("Setup Performance : " + (end - start));

long start2 = System.currentTimeMillis();
int matchingCount = 0;
for (int j =0; j < 500; j++) {
if (testList.contains(baseStr + generator.nextInt(5000))) {
matchingCount++;
}
}
long end2 = System.currentTimeMillis();
System.out.println("ArrayList Contains Performance : " + (end2 - start2));
}


[ 결과 (시간단위 : ns) ]


결과를 보게되면 실제 set, list에 데이터를 쌓을 때는 hashset이 약 2배 정도 오래 걸리는 것을 알 수 있다. 

(아마 내부적으로 해쉬함수를 돌려 넣기 때문일 것으로 추정)

하지만 contains메서드의 hashset은 거의 시간이 안걸린다고 볼 수 있는 반면에 list는 set에 비해서는 꽤나 걸리는 것으로 볼 수 있다. 

따라서 한 번 데이터를 초기화해놓고 get이나 contains메서드를 사용할 일이 많은 시스템이라면 list보다는 set으로 자료구조를 가져가는 편이 좋다고 할 수 있을 것 같다.


포스팅을 마치도록 하겠습니다. 감사합니다.

도움이 되셨다면 광고 한 번 클릭해주시는 센스^_^


참고 : https://www.baeldung.com/java-hashset-arraylist-contains-performance



반응형
반응형

스프링부트(SpringBoot)로 카프카에서 데이터를 consume에 처리해야 하는 일이 있어 작업 중 발생한 에러에 대해 간단히 남겨본다...

나중에 같은 실수를 하지 않기 위해...


일단 현재 사용하고 있는 카프카 버전은 0.8.2.0이고 spring-integration-kafka를 사용하였다.

실제 클라우데라에 설치된 카프카 버전


카프카 낮은 버전을 사용하고 있어 요즘 spring-kafka 연동 가이드로는 에러가 발생해 삽질을 좀 하였다...

시간이 된다면 0.8.2.2 consume 모듈을 git에 올려서 링크 걸어보도록 하겠다.


다음과 같이 KafkaConfig를 설정해주고


[ 스프링 설정 및 코드 ]

@EnableIntegration
@Configuration
public class KafkaIntegration {

private static final String BOOTSTRAP_SERVER = "serverIp:2181";
private static final String ZOOKEEPER_CONNECT = "serverIp:2181";

@Getter
@Component
public static class KafkaConfig {
private String topic = "data_log";
private String brokerAddress = BOOTSTRAP_SERVER;
private String zookeeperAddress = ZOOKEEPER_CONNECT;

KafkaConfig(){}

public KafkaConfig(String t, String b, String zk) {
this.topic = t;
this.brokerAddress = b;
this.zookeeperAddress = zk;
}
}
}


Consumer 빈 등록

@Configuration
public class ConsumerConfiguration {

@Autowired
private KafkaIntegration.KafkaConfig kafkaConfig;

@Bean
public IntegrationFlow consumer() {

KafkaHighLevelConsumerMessageSourceSpec messageSourceSpec = Kafka.inboundChannelAdapter(
new ZookeeperConnect(this.kafkaConfig.getZookeeperAddress()))
.consumerProperties(props -> props.put("auto.offset.reset", "smallest")
.put("auto.commit.interval.ms", "100"))
.addConsumer("ectc_dsp_test", metadata -> metadata.consumerTimeout(100)
.topicStreamMap(m -> m.put(this.kafkaConfig.getTopic(), 1))
.maxMessages(10).valueDecoder(String::new));

Consumer<SourcePollingChannelAdapterSpec> endpointConfigurer =
e -> e.poller(p -> p.fixedDelay(100));

return IntegrationFlows
.from(messageSourceSpec, endpointConfigurer)
.<Map<String, List<String>>>handle((payload, headers) -> {
payload.entrySet().forEach(
e -> System.out.println((e.getKey() + '=' + e.getValue())));
return null;
})
.get();
}

}


으로 등록 후 어플리케이션을 실행하면 다음과 같은 에러메시지가 발생하였다.


[ 발생한 에러메세지 ]

kafka.common.KafkaException: fetching topic metadata for topics [Set(dsp_log)] from broker [ArrayBuffer(id:245,host:eedkaf-dmp001.svr.net,port:9092, id:94,host:eedkaf-dmp002.svr.net,port:9092, id:95,host:eedkaf-dmp003.svr.net,port:9092)] failed

at kafka.client.ClientUtils$.fetchTopicMetadata(ClientUtils.scala:72) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.client.ClientUtils$.fetchTopicMetadata(ClientUtils.scala:93) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.consumer.ConsumerFetcherManager$LeaderFinderThread.doWork(ConsumerFetcherManager.scala:66) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.utils.ShutdownableThread.run(ShutdownableThread.scala:60) [kafka_2.11-0.8.2.0.jar:na]

Caused by: java.nio.channels.ClosedChannelException: null

at kafka.network.BlockingChannel.send(BlockingChannel.scala:100) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.producer.SyncProducer.liftedTree1$1(SyncProducer.scala:73) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.producer.SyncProducer.kafka$producer$SyncProducer$$doSend(SyncProducer.scala:72) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.producer.SyncProducer.send(SyncProducer.scala:113) ~[kafka_2.11-0.8.2.0.jar:na]

at kafka.client.ClientUtils$.fetchTopicMetadata(ClientUtils.scala:58) ~[kafka_2.11-0.8.2.0.jar:na]

... 3 common frames omitted


분명 로그를 봐도 내가 KafkaConfig에서 설정해준 서버 IP와 정상적인 커넥션을 맺었는데 왜이런단 말인가???

2018-11-20 12:12:05.668  INFO 87935 --- [161.26.70:2181)] org.apache.zookeeper.ClientCnxn          : Session establishment complete on server 내가지정한서버IP:2181, sessionid = 0x366e7594a3918e9


[ 해 결 ]

주키퍼(Zookeeper) 내부적으로 클러스터간 통신시 혹은 zookeeper to kafka간 통신시 서버의 IP보다는 호스트명으로 서로를 인지한다는 말?을 들은적이 있어 로컬 host파일에 서버의 호스트와 IP를  등록하고 다시 실행해보았더니 정상적으로 카프카에서 메세지를 consume하는 것을 확인할 수 있었다.


혹시나 다음과 같은 문제가 발생한다면 host파일에 서버의 호스트명과 IP를 등록 후 다시 해보길...


정확한 원인은 나중에 관련한 문서나 Zookeeper를 좀더 깊게 공부하게 되어 발견하게 된다면 추후 또 포스팅해보도록 하겠습니다. 


ref : https://spring.io/blog/2015/04/15/using-apache-kafka-for-integration-and-data-processing-pipelines-with-spring

반응형
반응형

일반적인 map.size() 말고 실제 메모리에 올라가는 map byte사이즈를 알고 싶은경우~


다음과 같이 사용하면 될듯


public static void size(Map map) {
try {
System.out.println("Index Size: " + map.size());
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
System.out.println("Data Size: " + baos.size());
} catch(IOException e){
e.printStackTrace();
}
}


baos.size() 메소드를 들어가보면 다음과 같이 bytes를 리턴한다.

/**
* Returns the current size of the buffer.
*
* @return the value of the <code>count</code> field, which is the number
* of valid bytes in this output stream.
* @see java.io.ByteArrayOutputStream#count
*/
public synchronized int size() {
return count;
}


확인할일이 있어서 보다가 정리

반응형
반응형

자바 Stream에서 Map, Object내부에 또 다른 Map, Set, List와 같은 Collection이 있을 때 각 value를 개별된 값으로 처리하고 싶은 경우



Student POJO

public class Student {

private String name;
private Set<String> book;

public void addBook(String book) {
if (this.book == null) {
this.book = new HashSet<>();
}
this.book.add(book);
}
//getters and setters

}



flatMap() and Set example

1차적으로 map으로 POJO에 Set에 담긴 Book데이터를 가져와 flatmap으로 펼친 후 list로 collect

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class TestExample2 {

public static void main(String[] args) {

LexiconMain.Student obj1 = new LexiconMain.Student();
obj1.setName("mkyong");
obj1.addBook("Java 8 in Action");
obj1.addBook("Spring Boot in Action");
obj1.addBook("Effective Java (2nd Edition)");

LexiconMain.Student obj2 = new LexiconMain.Student();
obj2.setName("zilap");
obj2.addBook("Learning Python, 5th Edition");
obj2.addBook("Effective Java (2nd Edition)");

List<LexiconMain.Student> list = new ArrayList<>();
list.add(obj1);
list.add(obj2);

List<String> collect =
list.stream()
.map(x -> x.getBook()) //Stream<Set<String>>
.flatMap(x -> x.stream()) //Stream<String>
.distinct()
.collect(Collectors.toList());

collect.forEach(x -> System.out.println(x));
}

}


Output

Spring Boot in Action
Effective Java (2nd Edition)
Java 8 in Action
Learning Python, 5th Edition


Stream관련 자세한 예제가 더 궁금하다면 아래 링크로


참고 : https://www.mkyong.com/java8/java-8-flatmap-example/

반응형
반응형

[ Java8 ] Stream Collectors toMap 사용시 Duplicate Key Error


DB로부터 데이터를 가져와 List에 담고 List에 담긴 데이터를 MAP에 담아 처리하는 작업이다.


List to Map 시 Key 값이 중복일 경우 Duplicate Key에러 발생

2018:08:08 19:47:49.947 ERROR --- [http-nio-80-exec-24] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.util.concurrent.CompletionException: java.lang.IllegalStateException: Duplicate key 31356962-6f5b-4280-9beb-f48d4c437695] with root cause

java.lang.IllegalStateException: Duplicate key 31356962-6f5b-4280-9beb-f48d4c437695

        at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)

        at java.util.HashMap.merge(HashMap.java:1254)



[ 에러가 발생했던 코드 ]

CompletableFuture<List<Partner>> matchedList = cookieMatchingRepository.getMatchedPartnerUserIdByBid(bid);
Map<String, String> matchedPartnerMap =
matchedList.get().stream().collect(Collectors.toMap(Partner::getPartnerId, Partner::getPartnerUserId));


[ 동일한 KEY값이 들어와도 처음 KEY-VALUE 값으로 처리하는 코드 (노란 부분이 바뀜) ]

CompletableFuture<List<Partner>> matchedList = cookieMatchingRepository.getMatchedPartnerUserIdByBid(bid);
Map<String, String> matchedPartnerMap = matchedList.get().stream()
.collect(Collectors.toMap(Partner::getPartnerId, Partner::getPartnerUserId, (p1, p2) -> p1));



반응형
반응형

스프링부트(SpringBoot) 순차적 방식과 비동기처리(asyn애노테이션), Completablefuture와 성능 비교

Spring Boot 기반의 웹 애플리케이션은 HTTP 요청이 들어왔을 때 내장된 서블릿 컨테이너(Tomcat)가 관리하는 독립적인 1개의 Worker 쓰레드에 의해 동기 방식으로 실행된다. 하지만 요청 처리 중 @Async가 명시된 메써드를 호출하면 앞서 등록한 ThreadPoolTaskExecutor 빈에 의해 관리되는 또 다른 독립적인 Worker 쓰레드로 실행된다. 
별도의 쓰레드로 동작하기에 원래의 요청 쓰레드는 그대로 다음 문장을 실행하여 HTTP 응답을 마칠 수 있다.


배경

리얼환경에서의 서비스를 위해 실제 환경(l4 - API서버 3)에서 어느정도의 트래픽을 안전하게 처리할 수 있는지에 대한 산정 필요(서버 도입 시점 등)'

해당 테스트는 단순 select로직을 통한 테스트로 참고 부탁드립니다.

메서드 내에서 여러 곳으로부터의 데이터를 받아와 처리하는 작업이 있는 경우 async애노테이션, CompletableFuture를 사용한 방식이 훨씬이

성능상 훨씬 많은 이점을 가져다 줄 수 있습니다. 

테스트 로직

기존로직

기존로직.png

변경로직 (@Asyn 애노테이션과 CompletableFuture를 통한 비동기 처리)

변경로직.png

쿼리 (위 : 변경쿼리, 아래 : 기존쿼리)

쿼리.png

성능측정

L4장비에 트래픽 부하(gatling) - 부하 툴로는 gatling을 사용하였습니다. 무료로 사용할 수 있는 툴로 쉽게 사용하실 수 있습니다.

1. Request : 9000, Duration 10초  - TPS 300

기존 로직
cm_9000_1.pngcm_9000_2.png

변경로직

asyn_9000_1.pngasyn_9000_2.png

2. Request : 24000, Duration 10초  - TPS 800

기존로직                                                                 변경로직(ThreadPool - 200)                                변경로직(ThreadPool - 400)


ThreadPool설정값 조정

스프링부트를 사용할 경우 별도 application.properties에 server.tomcat.max-threads 다음설정을 해주지 않는 경우

기본 max-thread의 개수는 200개가 된다. (The default value for max-threads is 200)

따라서 400으로 올려서 테스트 진행


스레드의 개수를 600으로 두고 테스트를 했을 떄는 다음과 같은 에러가 발생했다.

[java.util.concurrent.ThreadPoolExecutor@18da28a1[Running, pool size = 741, active threads = 741, queued tasks = 200, completed tasks = 207]] did not accept task: java.util.concurrent.CompletableFuture$AsyncSupply@47ff8bc8] with root cause

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$AsyncSupply@47ff8bc8 rejected from java.util.concurrent.ThreadPoolExecutor@18da28a1[Running, pool size = 741, active threads = 741, queued tasks = 200, completed tasks = 207]

        at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)

        at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)


3. 결론

1번 테스트(TPS 300) 결과를 보면 Asyn annotation과 Completablefuture를 사용한 쪽이 응답 속도가 훨씬 안정적으로 처리되는 것을 볼 수 있다.(99th percentile 기준)
2번 테스트 결과(TPS 800)를 봤을 때 1/3 정도의 요청이 fail나는 것을 확인 할 수 있었고 TPS 800 은 요청에 대한 fail비율과 응답속도(100ms 이내를 기준으로 한다)로 보았을 때 서버 3대로 처리할 수 있는 요청량이 아니라는 판단을 할 수 있다.
그 이후 TPS 400, 500, 600 700으로도 테스트 한 결과 안정적
(최대 50ms - 광고 비지니스 특성상)이내로 ADX, DSP에 값을 전달하기 위해서는 
최대 TPS 300을 기준으로 운영방침을 가져갈 수 있을 것 같다.

TPS 300 -> 한 서버당 하루요청량 2500만을 기준으로 둘 수 있겠다. 

(물론 카산드라 3.0에 데이터가 유입량과 로직 추가에 따른 부분을 항상 염두해 두어야 할 것이다.)



추가적으로, 비동기방식으로 처리를 했을 때 메모리, cpu사용률이 더 높은 것을 확인 할 수 있었다.

무작정 비동기방식이 좋다고 말할 수도 동기 방식이 좋다고도 말하기 힘들 것 같다.

단순한 작업은 동기 방식 한 메서드 내에서 순차적으로 이루어지지 않아도 되는 작업이 여러 개 있는 경우(DB호출, 외부 API호출 등)이 있는 경우에는

비동기 방식이 성능상 이점을 가져다 줄 수 있을 것이다.

반응형
반응형

보통 웹서비스에서 포트를 두 개 사용하는 경우는 http, https를 사용하는 경우이다.


보통은 ssl작업은 앞단에 웹서버(apache, nginx)를 두지 처리하는데 springboot만으로도 처리가 가능하다.


application.properties에 server.port=8080을 설정해두고 


추가로 8082 포트를 사용하고자 하면 다음과같이  ConfigurableEmbeddedServletContainer에


Connector에 추가로 사용하고자 하는 포트(port)를 설정해 셋팅해주면 된다.




이렇게 설정하고 실행시켜서 테스트해보면


localhost:8080, localhost:8082로 모두 서비스를 사용할 수 있다.


어떻게 언제 사용하냐고????


보통은 http, https 처리하는 경우, 그 외에는 사실 잘모르겠다...

반응형
반응형


Java8 Stream을 사용하다 보면 자주 발생할 수 있는 미묘한 실수 중에 하나는


이미 사용했던 Stream을 다시 사용하려고 하는 것이다. Stream은 오직 한 번만 사용할 수 있다.


Stream should be operated on (invoking an intermediate or terminal stream operation) only once. 


A Stream implementation may throw IllegalStateException if it detects that the Stream is being reused.


이미 사용했던 Stream을 또 사용하게 될 경우 다음과 같은 에러 로그를 발견할 수 있을 것이다.


java.lang.IllegalStateException: stream has already been operated upon or closed

at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)

at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)

at com.payco.cm.service.CacheService.cachingPartnerUrlMap(CacheService.java:31)

at com.payco.cm.service.CacheService$$FastClassBySpringCGLIB$$d06ada09.invoke(<generated>)

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)

at org.springframework.cache.interceptor.CacheInterceptor$1.invoke(CacheInterceptor.java:52)

at org.springframework.cache.interceptor.CacheAspectSupport.invokeOperation(CacheAspectSupport.java:344)

at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:407)

at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:326)

at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)



[ 문제가 발생했던 코드 ]



코드를 보면 Stream을 두 번 사용하고 있는 것을 볼 수있다.


로깅기능으로 잠깐 보려고 했던 코드가 문제가 되었다.


정리하자면

Stream shoud be used only Once!!!


반응형
반응형

[ Spring ] 문제해결 no suitable constructor found, can not deserialize from Object value 


API로 JSON 데이터 받아와 모델에 매핑시키는 부분에서 다음과 같은 에러가 발생하였다.


org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not construct instance of beomcess.coin.contractor.entity.Bittrex$BittrexCoin: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)

 at [Source: java.io.PushbackInputStream@423b2b62; line: 1, column: 41] (through reference chain: beomcess.coin.contractor.entity.Bittrex["result"]->java.util.ArrayList[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of beomcess.coin.contractor.entity.Bittrex$BittrexCoin: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)

 at [Source: java.io.PushbackInputStream@423b2b62; line: 1, column: 41] (through reference chain: beomcess.coin.contractor.entity.Bittrex["result"]->java.util.ArrayList[0])

at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:240)

at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:225)

at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95)

at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)


쉽게 말해 API 응답으로 받아와 JSON 데이터가 내가 매핑하고자 하는 Model에 알맞지 않다는 것이다...


API호출 결과 날아오는 JSON의 형태는 다음과 같았다.



내가 받고자 했던 JAVA MODEL의 형태는 다음과 같았다.


문제는 Result Array 내부에 있는 데이터들을 매핑해 오지 못해생겼던 것 같다.



일단 해결은 내부의 BittrexCoin 클래스에 static을 선언해 해결하였다.



이렇게 inner class를 static으로 변경하고 나니 정상적으로 데이터를 받아 올 수 있었다.


static 지시사의 역할은 메소드나 변수를 메모리에 로딩해서 다른 클래스가 이 클래스의 인스턴스를


생성하지 않고서도 사용할 수 있게 해주는 목적이다. 


RestTemplate으로 요청을 날리면서 동시에 내가 원하는 모델에 매핑해서 데이터를 가져오는데 static을 선언해주지 않았을 경우에


inner class인스턴스를 못만들어내서 매핑이 되지 않는 것 같다. 더 정확한 원인까지는 잘 모르겠다...


나중에 시간되면 더 파고들어볼만한 이슈인 것 같다.


혹시 원인에 대해 아시는분이 계시다면 댓글남겨주시면 감사하겠습니다. 







반응형

+ Recent posts