XML ๋ฐ์ดํ„ฐ๋ฅผ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ (feat. JAXB)

๋ธ”๋กœ๊ทธ ์˜ฎ๊ฒผ์Šต๋‹ˆ๋‹ค! ๐Ÿก’ integer.blog



XML ๋ฐ์ดํ„ฐ๋ฅผ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฌ์šฉํ•œ ๊ฒฝํ—˜์„ ์ •๋ฆฌํ•ด๋ณธ๋‹ค.

JSON ๋ฐ์ดํ„ฐ๋งŒ ๋‹ค๋ฃจ๋‹ค๊ฐ€ XML์„ ๋‹ค๋ฃจ์–ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ธˆ ์ƒ์†Œํ–ˆ๋‹ค. (์š”์ฆ˜ ๊ฐ™์€ ์„ธ์ƒ์— XML ์ด๋ผ๋‹ˆ!)
๊ทธ๋Ÿฐ๋ฐ ์•„์ง XML์„ ์‚ฌ์šฉํ•˜๋Š” API๋“ค์ด ๋งŽ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ , ํ•ญ๊ณต ๋ถ„์•ผ๋Š” ๊ฑฐ์˜ ๋ชจ๋“  API๊ฐ€ XML์ด๋ผ๊ณ  ํ•œ๋‹ค.

์ฒ˜์Œ์—๋Š” ํ•œ๊ตญ๊ฑฐ๋ž˜์†Œ์˜ ์ฃผ๊ฐ€์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ๋ผ๋Š” ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์ฐธ๊ณ ํ•˜์—ฌ
API๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” XML ๋ฐ์ดํ„ฐ๋ฅผ JSON Object๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , ์ด๋ฅผ ๋‹ค์‹œ Map ํ˜•ํƒœ๋กœ ๋ฐ”๊พธ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐœ๋ฐœํ–ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ด ๊ฒฝ์šฐ ๊ฐœ๋ฐœ์€ ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต์— ํ•œ๊ณ„๊ฐ€ ์žˆ์—ˆ๋‹ค.
๋ถ€ํŒ€์žฅ๋‹˜์œผ๋กœ๋ถ€ํ„ฐ XML ๋ฐ์ดํ„ฐ๋ฅผ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋‹ค๋ฃจ๋Š” Unmarshalling์ด๋ผ๋Š” ๊ฐœ๋…์„ ๋“ฃ๊ฒŒ ๋˜์—ˆ๊ณ ,
์ด๋ฅผ ์ง€์›ํ•ด์ฃผ๋Š” Java API์ธ JAXB(Java Architecture for XML Binding)๋ฅผ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.


JAXB ?

JAXB๋Š” Java ํด๋ž˜์Šค๋ฅผ XML๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” Marshalling๊ณผ XML์„ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” Unmarshalling ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

JAXB๋Š” JDK1.6์— ๋ฒˆ๋“ค๋ง๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— JDK 1.6 ์ด์ƒ์˜ ๋ฒ„์ „์ด๋ฉด ๋ณ„๋‹ค๋ฅธ ์„ค์ •์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
(JDK 1.6 ์ดํ•˜์˜ ๊ฒฝ์šฐ JAXB ๊ณต์‹ Repo์—์„œ ๋‹ค์šด๋ฐ›์•„์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š”๋ฐ ์•ˆํ•ด๋ด์„œ ๋ชจ๋ฅด๊ฒ ๋‹ค.)


์ด๋ฏธ์ง€ ์ถœ์ฒ˜: IBM Knowledge Center


๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด xjc ์Šคํ‚ค๋งˆ ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ํ†ตํ•ด XML ์Šคํ‚ค๋งˆ๋ฅผ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๊ณ ,
schemagen ์Šคํ‚ค๋งˆ ์ƒ์„ฑ๊ธฐ๋ฅผ ํ†ตํ•ด Java ๊ฐ์ฒด๋ฅผ XML ์Šคํ‚ค๋งˆ๋กœ ์ž‘์„ฑํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.


KRX(ํ•œ๊ตญ๊ฑฐ๋ž˜์†Œ) ์ฃผ๊ฐ€์ •๋ณด API

KRX(ํ•œ๊ตญ๊ฑฐ๋ž˜์†Œ)์˜ ์ฃผ๊ฐ€์ •๋ณด API๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ ๋ฐ์ดํ„ฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
(์ž„์‹œ๋กœ ๋„ค์ด๋ฒ„์˜ ์ข…๋ชฉ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.)

<?xml version="1.0" encoding="utf-8" ?>
<stockprice querytime="2019-06-03 14:18:12" >
	<TBL_DailyStock>
		<DailyStock day_Date="19/06/03" day_EndPrice="115,000" day_Dungrak="2" day_getDebi="3,500" day_Start="113,000" day_High="115,000" day_Low="112,000" day_Volume="270,570" day_getAmount="30,831,869,000"/>
		<DailyStock day_Date="19/05/31" day_EndPrice="111,500" day_Dungrak="5" day_getDebi="500" day_Start="111,500" day_High="112,500" day_Low="110,000" day_Volume="310,649" day_getAmount="34,620,804,500"/>
		<DailyStock day_Date="19/05/30" day_EndPrice="112,000" day_Dungrak="2" day_getDebi="2,500" day_Start="110,000" day_High="114,000" day_Low="109,500" day_Volume="378,379" day_getAmount="42,526,903,757"/>
		<DailyStock day_Date="19/05/29" day_EndPrice="109,500" day_Dungrak="2" day_getDebi="1,000" day_Start="109,000" day_High="111,000" day_Low="108,500" day_Volume="515,196" day_getAmount="56,529,874,000"/>
		<DailyStock day_Date="19/05/28" day_EndPrice="108,500" day_Dungrak="5" day_getDebi="2,000" day_Start="109,500" day_High="112,500" day_Low="108,500" day_Volume="1,490,526" day_getAmount="162,354,790,344"/>
		<DailyStock day_Date="19/05/27" day_EndPrice="110,500" day_Dungrak="5" day_getDebi="3,000" day_Start="112,500" day_High="113,000" day_Low="109,500" day_Volume="369,902" day_getAmount="41,005,007,000"/>
		<DailyStock day_Date="19/05/24" day_EndPrice="113,500" day_Dungrak="2" day_getDebi="500" day_Start="113,000" day_High="115,000" day_Low="112,500" day_Volume="408,081" day_getAmount="46,387,229,000"/>
		<DailyStock day_Date="19/05/23" day_EndPrice="113,000" day_Dungrak="3" day_getDebi="0" day_Start="112,500" day_High="114,000" day_Low="110,000" day_Volume="545,497" day_getAmount="61,145,647,703"/>
		<DailyStock day_Date="19/05/22" day_EndPrice="113,000" day_Dungrak="5" day_getDebi="1,500" day_Start="115,000" day_High="115,000" day_Low="111,000" day_Volume="567,069" day_getAmount="64,006,105,000"/>
		<DailyStock day_Date="19/05/21" day_EndPrice="114,500" day_Dungrak="5" day_getDebi="4,000" day_Start="117,500" day_High="119,500" day_Low="113,500" day_Volume="603,105" day_getAmount="70,058,497,000"/>
	</TBL_DailyStock>
	<TBL_AskPrice>
		<AskPrice member_memdoMem="๋ฏธ๋ž˜์—์…‹๋Œ€์šฐ" member_memdoVol="55,036" member_memsoMem="๋ชจ๊ฐ„์„œ์šธ" member_mesuoVol="51,883"/>
		<AskPrice member_memdoMem="ํ‚ค์›€์ฆ๊ถŒ" member_memdoVol="22,730" member_memsoMem="๋ฉ”๋ฆด๋ฆฐ์น˜" member_mesuoVol="26,117"/>
		<AskPrice member_memdoMem="NHํˆฌ์ž์ฆ๊ถŒ" member_memdoVol="19,927" member_memsoMem="์—์Šค์ง€" member_mesuoVol="15,435"/>
		<AskPrice member_memdoMem="๋ชจ๊ฐ„์„œ์šธ" member_memdoVol="16,103" member_memsoMem="์”จ์—˜" member_mesuoVol="13,549"/>
		<AskPrice member_memdoMem="CS์ฆ๊ถŒ" member_memdoVol="15,608" member_memsoMem="๋ฏธ๋ž˜์—์…‹๋Œ€์šฐ" member_mesuoVol="13,470"/>
	</TBL_AskPrice>
	<TBL_StockInfo JongName="NAVER๋ณดํ†ต์ฃผ" CurJuka="115,000" DungRak="2" Debi="3,500" PrevJuka="111,500" Volume="270,570" Money="30,831,869,000" StartJuka="113,000" HighJuka="115,000" LowJuka="112,000" High52="782,000" Low52="106,500" UpJuka="144,500" DownJuka="78,500" Per="25.92" Amount="164,813,395" FaceJuka="100" />
	<TBL_Hoga mesuJan0="3,709" mesuHoka0="113,500" mesuJan1="5,714" mesuHoka1="113,000" mesuJan2="3,504" mesuHoka2="112,500" mesuJan3="4,100" mesuHoka3="112,000" mesuJan4="3,813" mesuHoka4="111,500" medoJan0="4,389" medoHoka0="116,000" medoJan1="5,532" medoHoka1="115,500" medoJan2="14,353" medoHoka2="115,000" medoJan3="6,572" medoHoka3="114,500" medoJan4="734" medoHoka4="114,000"/>
	<TBL_TimeConclude>
		<TBL_TimeConclude time="14:18:20" negoprice="115,000" Dungrak="2" Debi="3,500" sellprice="115,000" buyprice="114,500" amount="4"/>
		<TBL_TimeConclude time="14:18:10" negoprice="114,500" Dungrak="2" Debi="3,000" sellprice="115,000" buyprice="114,500" amount="80"/>
		<TBL_TimeConclude time="14:18:00" negoprice="115,000" Dungrak="2" Debi="3,500" sellprice="115,000" buyprice="114,500" amount="108"/>
		<TBL_TimeConclude time="14:17:50" negoprice="115,000" Dungrak="2" Debi="3,500" sellprice="115,000" buyprice="114,500" amount="95"/>
		<TBL_TimeConclude time="14:17:40" negoprice="115,000" Dungrak="2" Debi="3,500" sellprice="115,000" buyprice="114,500" amount="216"/>
		<TBL_TimeConclude time="14:17:30" negoprice="114,500" Dungrak="2" Debi="3,000" sellprice="115,000" buyprice="114,500" amount="17"/>
		<TBL_TimeConclude time="14:17:20" negoprice="114,500" Dungrak="2" Debi="3,000" sellprice="115,000" buyprice="114,500" amount="15"/>
		<TBL_TimeConclude time="14:17:10" negoprice="114,500" Dungrak="2" Debi="3,000" sellprice="115,000" buyprice="114,500" amount="39"/>
		<TBL_TimeConclude time="14:17:00" negoprice="114,500" Dungrak="2" Debi="3,000" sellprice="115,000" buyprice="114,500" amount="451"/>
		<TBL_TimeConclude time="14:16:50" negoprice="115,000" Dungrak="2" Debi="3,500" sellprice="115,000" buyprice="114,500" amount="503"/>
		</TBL_TimeConclude>
	<stockInfo kosdaqJisu="697.33" kosdaqJisuBuho="2" kosdaqJisuDebi="0.86" starJisu="1328.27" starJisuBuho="2" starJisuDebi="10.63" jisu50="" jisu50Buho="" jisu50Debi="" myNowTime="2019/06/03 14:18:15" myJangGubun="์žฅ์ค‘" myPublicPrice="" krx100Jisu="4319.08" krx100buho="2" krx100Debi="60.71" kospiJisu="2063.14" kospiBuho="2" kospiDebi="21.40" kospi200Jisu="267.55" kospi200Buho="2" kospi200Debi="3.66"/>
</stockprice>

์ด ์ค‘ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์•˜๋‹ค.

<stockprice querytime="2019-05-22 15:19:56">

    <TBL_DailyStock>
        <DailyStock day_Date="19/05/22" day_EndPrice="113,000" day_Dungrak="5" day_getDebi="1,500" day_Start="115,000" day_High="115,000" day_Low="111,000" day_Volume="521,770" day_getAmount="58,887,446,500"/>
        <DailyStock day_Date="19/05/21" day_EndPrice="114,500" day_Dungrak="5" day_getDebi="4,000" day_Start="117,500" day_High="119,500" day_Low="113,500" day_Volume="603,105" day_getAmount="70,058,497,000"/>
        <DailyStock day_Date="19/05/20" day_EndPrice="118,500" day_Dungrak="5" day_getDebi="2,500" day_Start="121,500" day_High="121,500" day_Low="118,000" day_Volume="449,916" day_getAmount="53,573,016,000"/>
        <DailyStock day_Date="19/05/17" day_EndPrice="121,000" day_Dungrak="2" day_getDebi="2,000" day_Start="120,000" day_High="123,500" day_Low="118,500" day_Volume="439,545" day_getAmount="53,184,383,000"/>
        <DailyStock day_Date="19/05/16" day_EndPrice="119,000" day_Dungrak="5" day_getDebi="1,000" day_Start="120,500" day_High="121,000" day_Low="118,000" day_Volume="484,468" day_getAmount="57,871,810,000"/>
        <DailyStock day_Date="19/05/15" day_EndPrice="120,000" day_Dungrak="5" day_getDebi="2,000" day_Start="121,500" day_High="123,000" day_Low="120,000" day_Volume="460,936" day_getAmount="55,811,604,000"/>
        <DailyStock day_Date="19/05/14" day_EndPrice="122,000" day_Dungrak="5" day_getDebi="500" day_Start="121,000" day_High="123,500" day_Low="119,500" day_Volume="532,037" day_getAmount="64,845,489,250"/>
        <DailyStock day_Date="19/05/13" day_EndPrice="122,500" day_Dungrak="3" day_getDebi="0" day_Start="121,500" day_High="124,000" day_Low="121,500" day_Volume="343,827" day_getAmount="42,101,773,000"/>
        <DailyStock day_Date="19/05/10" day_EndPrice="122,500" day_Dungrak="2" day_getDebi="3,500" day_Start="120,500" day_High="123,000" day_Low="120,000" day_Volume="474,352" day_getAmount="57,723,276,000"/>
        <DailyStock day_Date="19/05/09" day_EndPrice="119,000" day_Dungrak="5" day_getDebi="4,500" day_Start="122,500" day_High="124,000" day_Low="119,000" day_Volume="799,208" day_getAmount="95,972,050,550"/>
    </TBL_DailyStock>

    <TBL_StockInfo
        JongName="NAVER๋ณดํ†ต์ฃผ"
        CurJuka="113,000"
        StockDungRak="5"
        Debi="1,500"
        PrevJuka="114,500"
        Volume="521,770"
        Money="58,887,446,500"
        StartJuka="115,000"
        HighJuka="115,000"
        LowJuka="111,000"
        High52="782,000"
        Low52="106,500"
        UpJuka="148,500"
        DownJuka="80,500"
        Per="25.47"
        Amount="164,813,395"
        FaceJuka="100" />

</stockprice>

์ฆ‰, ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” <TBL_DailyStock>๊ณผ <TBL_StockInfo> ๋‹จ 2๊ฐœ์˜ ํ…Œ์ด๋ธ”์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ ์ด ๋‘ ํ…Œ์ด๋ธ”์˜ ๋ชจ์–‘์ƒˆ๊ฐ€ ์กฐ๊ธˆ ๋‹ฌ๋ž๋‹ค.

<TBL_DailyStock>๋Š” ์ตœ๊ทผ 10์ผ์˜ DailyStock ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ๊ฐ DailyStock ์€ 9๊ฐœ์˜ Attribute๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฐ˜๋ฉด,
<TBL_StockInfo>๋Š” 17๊ฐœ์˜ Attribute๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ๋‹ค.

JAXB Unmarshalling์˜ ํ•ต์‹ฌ์€ ์ด XML์Šคํ‚ค๋งˆ ๋ชจ์–‘๋Œ€๋กœ Java ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๋ฉด,
ํด๋ž˜์Šค์˜ ๊ฐ ํ•„๋“œ์— ํ•ด๋‹นํ•˜๋Š” XML์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”์ธ๋”ฉ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ XML ์Šคํ‚ค๋งˆ์˜ ๋ชจ์–‘์ƒˆ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•œ ํ›„์— Java Class๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค.


Java Class ์ƒ์„ฑ

์šฐ์„ , ์ตœ์ƒ์œ„ ํƒœ๊ทธ๋Š” <stockprice>์ด๋ฏ€๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด StockPrice ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ ,
JAXB ๋ฐ”์ธ๋”ฉ ๋Ÿฐํƒ€์ž„ API๊ฐ€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก @XmlRootElement(name = "stockprice") ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ ๋งคํ•‘ ์ •๋ณด๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ <stockprice>์˜ ๊ฒฝ์šฐ Attribute๋กœ querytime์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ ,
2๊ฐœ์˜ ํ…Œ์ด๋ธ”(<TBL_DailyStock>, <TBL_StockInfo>)์„ ๋‚ดํฌํ•˜๊ณ  ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ querytime, TBL_DailyStock, TBL_StockInfo ๋ฅผ ํ•„๋“œ๋กœ ๋งŒ๋“ค๊ณ  ๊ฐ๊ฐ
@XmlAttribute์™€ @XmlElement ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ XML ์Šคํ‚ค๋งˆ์™€ ๋ชจ์–‘์ƒˆ๋ฅผ ๋งž์ถ”์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  TBL_DailyStock๊ณผ TBL_StockInfo๋Š” ๋‚ด๋ถ€์— ๋ฐ์ดํ„ฐ ์…‹์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ Inner Class๋กœ ๋งŒ๋“ค๊ณ ,
@XmlRootElement ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

/**
 * <stockprice querytime="...">
 *     <TBL_DailyStock></TBL_DailyStock>
 *     <TBL_StockInfo></TBL_StockInfo>
 * </stockprice>
 */
@Getter
@ToString
@XmlRootElement(name = "stockprice")
public class StockPrice {

    @XmlAttribute(name = "querytime")
    private String querytime;

    @XmlElement(name = "TBL_DailyStock")
    private TBL_DailyStock tbl_dailyStock;

    @XmlElement(name = "TBL_StockInfo")
    private TBL_StockInfo tbl_stockInfo;
    
    // ์˜ˆ์™ธ์ฒ˜๋ฆฌ, ์œ ํšจ์„ฑ๊ฒ€์ฆ ๊ด€๋ จ ์ฝ”๋“œ ์ƒ๋žต

    @Getter
    @ToString
    @XmlRootElement(name = "TBL_DailyStock")
    public static class TBL_DailyStock {

        @XmlElement(name = "DailyStock")
        private List<DailyStock> dailyStocks;

	    // ์˜ˆ์™ธ์ฒ˜๋ฆฌ, ์œ ํšจ์„ฑ๊ฒ€์ฆ ๊ด€๋ จ ์ฝ”๋“œ ์ƒ๋žต
    }

    @Getter
    @ToString
    @XmlRootElement(name = "TBL_StockInfo")
    public static class TBL_StockInfo {

        @XmlAttribute(name = "JongName")
        private String jongName;

        @XmlAttribute(name = "CurJuka")
        private String curJuka;

        private String dungRak;

        @XmlAttribute(name = "DungRak")
        public void setDungRak(String dungRak) {

            if("2".equals(dungRak)) {
                this.dungRak = StockDungRak.UP.getName();
            } else if("3".equals(dungRak)) {
                this.dungRak = StockDungRak.FLAT.getName();
            } else if("5".equals(dungRak)) {
                this.dungRak = StockDungRak.DOWN.getName();
            }
        }
	    // ์ดํ•˜ ํ•„๋“œ ์ƒ๋žต
        // ์˜ˆ์™ธ์ฒ˜๋ฆฌ, ์œ ํšจ์„ฑ๊ฒ€์ฆ ๊ด€๋ จ ์ฝ”๋“œ ์ƒ๋žต
    }
}

์ด ๋•Œ, ์œ„์˜ ์ฝ”๋“œ์˜ dungRak ์ฒ˜๋Ÿผ Setter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ๊ฐ€๊ณตํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ,
ํ•„๋“œ ๋Œ€์‹  Setter์— @XmlAttribute ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์•ผ ๋ฐ์ดํ„ฐ๊ฐ€ ์ œ๋Œ€๋กœ ๋ฐ”์ธ๋”ฉ๋œ๋‹ค.

...

@XmlRootElement(name = "TBL_DailyStock")
    public static class TBL_DailyStock {

        @XmlElement(name = "DailyStock")
        private List<DailyStock> dailyStocks;

    }

...

๊ทธ๋Ÿฐ๋ฐ ์œ„์˜ ์ฝ”๋“œ์—์„œ ์ฒ˜๋Ÿผ DailyStock๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” TBL_DailyStock ํด๋ž˜์Šค๋ฅผ ์–ด๋–ป๊ฒŒ ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š”์ง€ ์กฐ๊ธˆ ํ—ท๊ฐˆ๋ ธ์—ˆ๋Š”๋ฐ,
DailyStock์€ ๊ทธ ์ž์ฒด๋กœ 9๊ฐœ์˜ Attribute๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์•„๋ž˜์™€ ๊ฐ™์ด ๋”ฐ๋กœ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ–ˆ๋‹ค.

/**
 * <stockprice>
 *     <TBL_DailyStock>
 *         <DailyStock></DailyStock>
 *         <DailyStock></DailyStock>
 *         ...
 *     </TBL_DailyStock>
 * </stockprice>
 */
@Getter
@ToString
@XmlRootElement(name = "DailyStock")
public class DailyStock {

    @XmlAttribute(name = "day_Date")
    private String day_Date;

    @XmlAttribute(name = "day_EndPrice")
    private String day_EndPrice;

    private String day_getAmount;

    @XmlAttribute(name = "day_getAmount")
    public void setDay_getAmount(String day_getAmount) {

        // ๊ฑฐ๋ž˜๋Œ€๊ธˆ(๋ฐฑ๋งŒ) ๋‹จ์œ„ ์ ˆ์‚ญ
        Long digit = Long.parseLong(day_getAmount.replaceAll(",", "")) / 1000000;
        this.day_getAmount = String.format("%,d", digit); // "%,d", it tells the method to put comma separator for each 3 digits
    }
    
    // ... ์ดํ•˜ ํ•„๋“œ ์ƒ๋žต
}

DailyStock์€ 17๊ฐœ์˜ Attribute๋งŒ ๋‚ดํฌํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ๋ชจ๋“  ํ•„๋“œ์— @XmlAttribute๋ฅผ ๋ถ™์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


Unmarshalling ์‹คํ–‰

์ด ์˜ˆ์ œ์˜ ๊ฒฝ์šฐ ํ”„๋กœ์ ํŠธ ๋‚ด์—์„œ Map์œผ๋กœ๋งŒ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— Map์„ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ
Unmarshalling์„ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ Map์„ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

XML์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•  Java ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ–ˆ์œผ๋‹ˆ Unmarshalling์„ ์‹คํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.(์ˆœ์„œ๋Š” ์ƒ๊ด€์—†๋‹ค.)

Unmarshalling ๊ณผ์ •

  1. ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•  Map ์ƒ์„ฑ
  2. ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ XML ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” URL์„ ์ž…๋ ฅํ•˜๊ณ , XML ๋ฐ์ดํ„ฐ๋ฅผ String์œผ๋กœ ์ €์žฅํ•œ๋‹ค.
  3. JAXB.newInstance(StockPrice.class)๋ฅผ ํ†ตํ•ด JAXB Context๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  4. ์ƒ์„ฑ๋œ JAXB Context๋กœ Unmarshaller๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  5. ์ƒ์„ฑ๋œ Unmarshaller๋กœ unmarshall ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ String์— ์ €์žฅํ•œ XML ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์„œ Java ๊ฐ์ฒด(StockPrice)์— ๋ฐ”์ธ๋”ฉํ•œ๋‹ค.
  6. Java ๊ฐ์ฒด๋ฅผ Map์— ๋‹ด์•„ ํ™”๋ฉด๋‹จ์— ์†ก์ถœํ•œ๋‹ค.
@Controller("/ir_stock")
public Map<String, StockPrice> krxParser(HttpServletRequest request) {

    Map<String, StockPrice> map = new HashMap<>();

    try {
        String html = StockUtil.getHtml("http://asp1.krx.co.kr/servlet/krx.asp.XMLSise?code=035420"); // 035420 = ๋„ค์ด๋ฒ„์˜ ์ข…๋ชฉ์ฝ”๋“œ

        JAXBContext jaxbContext = JAXBContext.newInstance(StockPrice.class); // JAXB Context ์ƒ์„ฑ
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); // Unmarshaller Object ์ƒ์„ฑ
        StockPrice stockPrice = (StockPrice) unmarshaller.unmarshal(new StringReader(html)); // unmarshall ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ

        stockPrice.validation();
        map.put("stockprice", stockPrice);

        return map;

    } catch (JAXBException | IOException e) {
        logger.error("KRX API ์˜ˆ์™ธ ๋ฐœ์ƒ", e);
        map.put("stockprice", StockPrice.emptyStockPrice()); // ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ, ๋นˆ ๊ฐ์ฒด ๋ฐ˜ํ™˜
        return map;
    }
}

์œ„์˜ ์ฝ”๋“œ์—์„œ XML ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์„œ String์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” getHtml() ๋ฉ”์†Œ๋“œ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค์— ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ–ˆ๋‹ค.

public static String getHtml(String url) throws IOException {

    URL targetUrl = new URL(url);

    try (BufferedReader reader = new BufferedReader(new InputStreamReader(targetUrl.openStream()))){
        StringBuffer html = new StringBuffer();
        String tmp;

        while ((tmp = reader.readLine()) != null) {
            html.append(tmp);
        }

        return html.toString();
    }
}

๋งˆ์ง€๋ง‰์œผ๋กœ, KRX API์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ค‘ DungRak(๋“ฑ๋ฝ)์˜ ๊ฒฝ์šฐ ์ƒ์Šน, ๋™์ผ, ํ•˜๋ฝ์„ ‘2’, ‘3’, ‘5’๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
๋•Œ๋ฌธ์— ์ˆซ์ž๋งŒ ๋ณด๊ณ ๋Š” ์ƒ์Šน์ธ์ง€ ํ•˜๋ฝ์ธ์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ
์•„๋ž˜์™€ ๊ฐ™์ด Enum ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ•˜์—ฌ ์˜๋ฏธ๋ฅผ ๋ช…์‹œํ–ˆ๋‹ค.

/**
 *
 * API์—์„œ๋Š”
 * ์ƒ์Šน = "2"
 * ๋ณ€ํ™”์—†์Œ = "3"
 * ํ•˜๋ฝ = "5"
 */
public enum StockDungRak {

    FLAT("flat"),
    UP("up"),
    DOWN("down");

    private String name;

    StockDungRak(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}


๊ฒฐ๊ณผ

ํ•ด๋‹น ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

XML ๋ฐ์ดํ„ฐ๋ฅผ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฌ์šฉํ•ด๋ณด๋‹ˆ, (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์„ž์—ฌ์žˆ์–ด ์ด ๊ธ€์—๋Š” ๋‹ด์ง€ ๋ชปํ–ˆ์ง€๋งŒ)
ํ™•์‹คํžˆ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต์„ ๋Šฅ๋™์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ข‹์•˜๋‹ค.

*Reference