[개인 프로젝트] KOPIS 공연 Open API 가져오기, XML JSON 파싱, DB 저장, 스프링 배치(Spring Batch)
my code archive
article thumbnail
반응형

내가 프로젝트를 시작한지 일주일 되었는데 글을 이제서야 쓰는 이유는...?

이 작업만 거의 일주일이 걸렸기 때문이다...ㅎㅎㅎ

Open API를 가져다 써본 경험이 없어서 처음이었고 + 보통 JSON 형식이던데

KOPIS API는 XML형식이라 파싱하는 것부터 애를 먹었다ㅠㅠ

그리고 DB 넣는 거!!! 이게 너무너무 어려웠다...

하고 나니 간단한데...

암튼 구글링을 했을 때 정보가 부족하다는 생각이 들어서 이것부터 정리를 해보려고 한다.


🤍XML 형식의 Open API 데이터 JSON 형식으로 파싱하기

1. 가장 먼저 Open API를 사용하기 위해 필요한 서비스키를 신청한다.

이 과정은 간단하기 때문에 생략.

 

2. 그다음으로 사이트에 개발에 필요한 가이드라인이 올라와 있다. 나는 뮤지컬 목록을 불러올 것이므로 [공연 목록 조회 서비스] 가이드를 보고 따라하면 된다.

가이드대로 그대로 따라하기만 하면 되는데! 항목 구분에서 필수라고 적혀있는 항목은 URL에 꼭 포함시켜서 조회를 해주어야 한다. URL 형식은 요청 URL 예제대로 따라하면 된다.

테스트를 해보면 이렇게 xml로 나온다. 

3. 이제 이걸 JSON 형식으로 바꾸어야 한다. 이것을 '파싱'이라고 한다.

JSON 파싱은 두 가지 라이브러리를 사용했다.

  implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1'
  implementation group: 'org.json', name: 'json', version: '20090211'

 

4.MusicalApi.java

//보안을 위해 application.properties에 설정을 해두고
#API 인증키
api.serviceKey='자신의 서비스키'

//@Value 어노테이션으로 가져오기
@Value("${api.serviceKey}")
    private String apiKey;

위에서 테스트 조회했던 것처럼 작성해준다. 조회 기간은 3개월로 잡았다.

@Component
@RequiredArgsConstructor
public class MusicalApi {

    @Value("${api.serviceKey}")
    private String apiKey;

    private final MusicalRepository musicalRepository;

    public void callMusicalApiJson(){

        StringBuffer result = new StringBuffer();
        String jsonPrintStr = null;

        try {
            //3개월 기간 설정
            SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
            Date currentDate = new Date();
            Calendar cal = Calendar.getInstance();
            cal.setTime(currentDate);
            cal.add(Calendar.MONTH,3);
            String stdate = formatter.format(currentDate);
            String eddate = formatter.format(cal.getTime());

            String urlStr = "http://www.kopis.or.kr/openApi/restful/pblprfr?service="+
                    apiKey+
                    "&stdate="+stdate+
                    "&eddate="+eddate+
                    "shcate=AAAB"+  /* 장르 : 뮤지컬 */
                    "&signgucode=11"+   /* 지역 : 서울 */
                    "&rows=10"+
                    "&cpage=1";
                    
           URL url = new URL(urlStr);

            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.connect();

5. 대망(?)의 파싱 코드...

XML에서 JSON으로 파싱하는 XML.toJSONObject()는 org.json에서만 제공해서

두 가지 라이브러리를 사용했다.. 이게 맞는 방법인지 모르겠지만? 삽질끝에 찾아낸 해결책...

맨 마지막줄에는 insert하기 위해 JPA의 save를 사용해주었다.

/* XML to JSON 파싱 */
            BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream());
            BufferedReader br = new BufferedReader(new InputStreamReader(bis,"UTF-8"));
            String returnLine;
            while ((returnLine=br.readLine())!=null){
                result.append(returnLine);
            }
            JSONObject jsonObject = XML.toJSONObject(result.toString());
            jsonPrintStr = jsonObject.toString();

            System.out.println(jsonPrintStr);

            /* DB 저장 */
            JSONParser jsonParser = new JSONParser();   //JSON Parser 객체 생성
            Object obj = jsonParser.parse(jsonPrintStr);    //Parser로 문자열 데이터를 객체로 변환
            org.json.simple.JSONObject _jsonObject = (org.json.simple.JSONObject) obj;  //파싱한 obj를 JSONObject 객체로 변환

            //차근차근 파싱하기
            org.json.simple.JSONObject parseResult = (org.json.simple.JSONObject) _jsonObject.get("dbs");
            ObjectMapper objectMapper = new ObjectMapper();
            //ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
            JSONArray parseMusicalList = (JSONArray) parseResult.get("db");
            for(int i=0;i<parseMusicalList.size();i++){
                org.json.simple.JSONObject dailyMusical = (org.json.simple.JSONObject) parseMusicalList.get(i);
                //JSON object -> Java Object(Entity) 변환
                Musical musical = objectMapper.readValue(dailyMusical.toString(),Musical.class);
                //insert
                musicalRepository.save(musical);

 

여기까지 작업하면 XML 형식으로 받아온 Open API를 JSON 형식으로 파싱하는 작업은 끝이다!!

이제 대망의 INSERT....


🤍조회해온 Open API 데이터 DB에 저장하기

 

6.Entity에 해당하는 Musical.java

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Musical {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "mcode")
    private Long mcode;

    private String mt20id;  //뮤지컬코드
    private String prfnm;   //공연명
    private String fcltynm; //공연장명
    private String poster;  //포스터 경로

    private String prfstate;    //공연 중 상태
}

XML의 각 태그 명칭과 일치하게 작성해 주었다.

 

7. Spring JPA 관련 MusicalRepository 작성

public interface MusicalRepository extends JpaRepository<Musical,Long> {

}

 

8. 서비스단 작성

/*MusicalService*/
public interface MusicalService {
    public void insertMusical();
}


/*MusicalServiceImpl*/
@Service
@RequiredArgsConstructor
@Log4j2
public class MusicalServiceImpl implements  MusicalService{

    private final MusicalApi musicalApi;
    private final MusicalRepository musicalRepository;


    public void insertMusical(){
        musicalApi.callMusicalApiJson();
    }
 }

 

다량의 데이터를 insert하기 위해 스프링 배치를 사용해 주었다.

 

💡 스프링 배치(Spring Batch)란?

  • 로깅/추적, 트랜잭션 관리, 작업 재시작, 건너뛰기 등 대용량 데이터 처리에 필요한 기능들을 제공함.
  • 배치가 실패하여 작업 재시작 시에도 처음부터가 아닌, 실패한 지점부터 실행을 하게됨.
  • 중복 실행을 방지하기 위해 성공 이력이 있는 Batch는 동일한 Parameters로 실행 시 Exception이 발생함.

9. Spring Batch 관련 build.gradle 의존성 작성

implementation 'org.springframework.boot:spring-boot-starter-batch'
testImplementation 'org.springframework.batch:spring-batch-test'

 

10. BatchConfig.java 작성

@Slf4j
@RequiredArgsConstructor
@Configuration
public class BatchConfig {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final MusicalService musicalService;

    @Bean
    public Job testJob(){
        return jobBuilderFactory.get("testJob")
                .start(testStep(null))
                .build();
    }

    @Bean
    @JobScope
    public Step testStep(@Value("#{jobParameters[testParameter]}") String testParameter){
        return stepBuilderFactory.get("testStep")
                .tasklet((contribution, chunkContext) -> {
                    musicalService.insertMusical();
                    return RepeatStatus.FINISHED;
                }).build();
    }
}

여기까지 코드를 작성하고 실행했을 때 json 형식으로 파싱은 잘 되었는데 에러가 발생했다.

💡에러 원인은 json 형식 데이터 중 Entity에 미포함시킨 데이터가 있기 때문이었다.

해결 방법은 엔티티에 아래 코드를 추가해 준다.(선언한 필드 이외 모든 요소 제외)

@JsonIgnoreProperties(ignoreUnknown = true)

 

배치 관련 testParameter에 날짜를 추가하고 다시 실행을 해보면!!

💡최종 결과

INSERT 성공!

 

배치 관련 테이블도 잘 생성되었다.

반응형
profile

my code archive

@얼레벌레 개발자👩‍💻

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

반응형