[SpringBatch 실습] 25. xml 파일 읽어서 출력하는 배치(StaxEventItemReader), josn 파일 읽어서 출력하는 배치(JsonItemReader) 생성해보기

반응형
728x90
반응형

Xml 파일 읽어오기

resources/item51/customer.xml
<?xml version="1.0" encoding="UTF-8" ?>
<customers>
    <customer id="1">
        <id>1</id>
        <name>hong gil dong1</name>
        <age>40</age>
    </customer>
    <customer>
        <id>2</id>
        <name>hong gil dong2</name>
        <age>42</age>
    </customer>
    <customer>
        <id>3</id>
        <name>hong gil dong3</name>
        <age>43</age>
    </customer>
</customers>

 

Customer.java
package com.project.springbatch._52_reader_json;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Customer {
    private final long id;
    private final String name;
    private final int age;
}

 

build.gradle
implementation 'org.springframework:spring-oxm:5.3.7'
implementation 'com.thoughtworks.xstream:xstream:1.4.16'

 

 

StaxEventItemReader

STAX 방식으로 XML 문서를 처리하는 ItemReader이다.

XML을 읽어 자바 객체로 매핑하고 자바 객체를 XML로 쓸수 있는 트랜잭션 구조를 지원한다.

<customer id="1"> -- 첫번째 fragment
    <id>1</id>
    <name>hong gil dong1</name>
    <age>40</age>
</customer>
<customer> -- 두번째 fragment
    <id>2</id>
    <name>hong gil dong2</name>
    <age>42</age>
</customer>
<customer> -- 세번째 fragment
    <id>3</id>
    <name>hong gil dong3</name>
    <age>43</age>
</customer>

 

▶ STAX 아키텍처

- XML 전체 문서가 아닌 조각 단위로 구문을 분석하여 처리한다.

- 루트 엘리먼트 사이의 있는 것들은 모두 하나의 조각(fragment)을 구성한다.

- 조각을 읽을때 DOM의 poll 방식을 사용하고 조각을 객체로 바인딩 처리하는 것은 SAX의 push 방식을 사용한다.

- frament를 객체로 바인딩하는 작업을 Spring OXM 기술에 위임한다.

 

 Spring OXM

- 스프링의 Object XML Mapping 기술이다.

- Marshaller.marshall : 객체를 XML로 직렬화한다.

- Unmarchaller.unmashall : XML을 객체로 역직렬화한다.

 

 

Job 생성

XMLConfiguration.java
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.xml.StaxEventItemReader;
import org.springframework.batch.item.xml.builder.StaxEventItemReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.xstream.XStreamMarshaller;

import java.util.HashMap;
import java.util.Map;

/*
--job.name=xmlBatchJob
 */
@RequiredArgsConstructor
@Configuration
public class XMLConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job xmlBatchJob() {
        return jobBuilderFactory.get("xmlBatchJob")
                .incrementer(new RunIdIncrementer())
                .start(xmlBatchStep1())
                .build();
    }

    @Bean
    public Step xmlBatchStep1() {
        return stepBuilderFactory.get("xmlBatchStep1")
                .<Customer, Customer>chunk(3)
                .reader(customXmlItemReader())
                .writer(customXmlItemWriter())
                .build();
    }

    @Bean
    public StaxEventItemReader<Customer> customXmlItemReader() {
        return new StaxEventItemReaderBuilder<Customer>()
                .name("customXmlItemReader")
                .resource(new ClassPathResource("item51/customer.xml"))
                .addFragmentRootElements("customer")
                .unmarshaller(itemMarshaller())
                .build();
    }

    @Bean
    public ItemWriter<Customer> customXmlItemWriter() {
        return items -> {
            for (Customer item : items) {
                System.out.println(item.toString());
            }
        };
    }

    @Bean
    public XStreamMarshaller itemMarshaller() {
        Map<String, Class<?>> aliases = new HashMap<>();

        aliases.put("customer", Customer.class); // tag 전체 타입
        aliases.put("id", Long.class); // 두번째부터는 각 항목의 타입
        aliases.put("name", String.class);
        aliases.put("age", Integer.class);

        XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
        xStreamMarshaller.setAliases(aliases);

        return xStreamMarshaller;
    }
}

1) 파일 경로 지정

.resource(new ClassPathResource("item51/customer.xml"))

 

2) root element 지정

.addFragmentRootElements("customer")
<?xml version="1.0" encoding="UTF-8" ?>
<customers>
    ...
</customers>

 

3) xml 파일 읽어온 데이터를 객체로 언마샬링

.unmarshaller(itemMarshaller())

▶ StaxEventItemReader.java > doRead()

@Nullable
@Override
protected T doRead() throws IOException, XMLStreamException {

   if (noInput) {
      return null;
   }

   T item = null;

   boolean success = false;
   try {
      success = moveCursorToNextFragment(fragmentReader);
   }
   catch (NonTransientResourceException e) {
      // Prevent caller from retrying indefinitely since this is fatal
      noInput = true;
      throw e;
   }
   if (success) {
      fragmentReader.markStartFragment();

      try {
         @SuppressWarnings("unchecked")
         T mappedFragment = (T) unmarshaller.unmarshal(StaxUtils.createStaxSource(fragmentReader));
         item = mappedFragment;
      }
      finally {
         fragmentReader.markFragmentProcessed();
      }
   }

   return item;
}

 

4) itemMashaller() 메서드

@Bean
public XStreamMarshaller itemMarshaller() {
    Map<String, Class<?>> aliases = new HashMap<>();

    aliases.put("customer", Customer.class); // tag 전체 타입
    aliases.put("id", Long.class); // 두번째부터는 각 항목의 타입
    aliases.put("name", String.class);
    aliases.put("age", Integer.class);

    XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
    xStreamMarshaller.setAliases(aliases);

    return xStreamMarshaller;
}

▶ xStreamMashaller 객체

 

Job 수행 결과
Customer(id=1, name=hong gil dong1, age=40)
Customer(id=2, name=hong gil dong2, age=42)
Customer(id=3, name=hong gil dong3, age=43)

 

 

 

Json 파일 읽어오기

resources/item52/customer.json
[
  {
    "id" : 1,
    "name" : "hong gil dong1",
    "age" : 41
  },
  {
    "id" : 2,
    "name" : "hong gil dong2",
    "age" : 42
  },
  {
    "id" : 3,
    "name" : "hong gil dong3",
    "age" : 43
  }
]

 

Customer.java
package com.project.springbatch._52_reader_json;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    private long id;
    private String name;
    private int age;
}

 

 

JsonItemReader

Json 데이터의 Parsing과 Biding을 JsonObjectReader 인터페이스 구현체에 위임하여 처리하는 ItemReader다.

두 구현체의 사용이 가능하다.

jsonObjectReader
JacksonJsonObjectReader
GsonJsonObjectReader

 

 

Job 생성

JsonConfiguration.java
package com.project.springbatch._52_reader_json;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.json.JacksonJsonObjectReader;
import org.springframework.batch.item.json.JsonItemReader;
import org.springframework.batch.item.json.builder.JsonItemReaderBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@RequiredArgsConstructor
@Configuration
public class JsonConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job jsonBatchJob() {
        return jobBuilderFactory.get("jsonBatchJob")
                .incrementer(new RunIdIncrementer())
                .start(jsonBatchStep1())
                .build();
    }

    @Bean
    public Step jsonBatchStep1() {
        return stepBuilderFactory.get("jsonBatchStep1")
                .<Customer, Customer>chunk(3)
                .reader(customJsonItemReader())
                .writer(customJsonItemWriter())
                .build();
    }

    @Bean
    public JsonItemReader<Customer> customJsonItemReader(){
        return new JsonItemReaderBuilder<Customer>()
                .name("customJsonItemReader")
                // JsonItemReader > doRead() > JacksonJsonObjectReader > read() 호출
                .jsonObjectReader(new JacksonJsonObjectReader<>(Customer.class))
                .resource(new ClassPathResource("item52/customer.json"))
                .build();
    }

    @Bean
    public ItemWriter<Customer> customJsonItemWriter() {
        return items -> {
            for (Customer item : items) {
                System.out.println(item.toString());
            }
        };
    }
}

1) jsonObjectReader()

제공되는 두가지 구현체인 JacksonJsonObjectReader, GsonJsonObjectReader 중 JacksonJsonObjectReader을 선택했다.

.jsonObjectReader(new JacksonJsonObjectReader<>(Customer.class))

▶ JsonItemReader.doRead()

@Nullable
@Override
protected T doRead() throws Exception {
   return jsonObjectReader.read();
}

itemType 은 Customer.java 타입이다.

▶ JacksonJsonObjectReader.read()

@Nullable
@Override
public T read() throws Exception {
   try {
      if (this.jsonParser.nextToken() == JsonToken.START_OBJECT) {
         return this.mapper.readValue(this.jsonParser, this.itemType);
      }
   } catch (IOException e) {
      throw new ParseException("Unable to read next JSON object", e);
   }
   return null;
}

결론적으로 ObjectMapper._readValue() 메서드를 호출한다.

 

 

Job 수행 결과
Customer(id=1, name=hong gil dong1, age=41)
Customer(id=2, name=hong gil dong2, age=42)
Customer(id=3, name=hong gil dong3, age=43)

 

 

반응형

Designed by JB FACTORY