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)