[Spring Batch 5] CSV, XML, Json 조회 - ItemReader 작성법

식빵·2024년 9월 22일
0

spring-batch-5

목록 보기
2/4

Spring Batch 에서 제공하는 ItemReader 구현체를 통해서
대표적인 파일 포맷인 CSV, XML, JSON 을 조회하기 위한 코드 작성법을 알아봅시다.


CSV 파일

테스트 파일: customer.csv

name,age,year
user01,30,2000
user02,31,2001
user03,32,2002
user04,33,2003
user05,34,2004
user06,35,2005
user07,36,2006
user08,37,2007
user09,38,2008
user10,39,2009

FlatFileItemReader 사용

package coding.toast.batch.flat_file;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@RequiredArgsConstructor
@Slf4j
public class FlatFileConfiguration {
	
	private final JobRepository jobRepository;
	private final PlatformTransactionManager platformTransactionManager;
	
	@Bean("FlatFileConfiguration-job-01")
	public Job job() {
		return new JobBuilder("FlatFileConfiguration-job-01", jobRepository)
			.start(step1())
			.build();
	}
	
    // Reader 작성법1: 직접 세팅하는 법
	@Bean("FlatFileConfiguration-item-reader-01")
	public ItemReader<Customer> itemReader() {
		
		FlatFileItemReader<Customer> itemReader = new FlatFileItemReader<>();
		itemReader.setResource(new ClassPathResource("customer.csv"));
		
        // LineMapper => 하나의 라인을 읽어서 원하는 도메인으로 변경하는 역할
        //            => 내부적으로 LineTokenizer, FieldSetMapper 을 사용함
        
        // LineTokenizer : 
        // 하나의 라인을 받으면, 그걸 특별한 조건 (ex: 구분자)에 따라 문자르
        // 토큰화한다. 토큰화한 정보들은 모아서 FieldSet 인스턴스에 저장
        // 
		
        // FieldSetMapper : LineTokenizer 가 반환한 FieldSet 을 활용하여
        // 원하는 도메인 객체로 변환하는 작업을 담당한다.
        
		DefaultLineMapper<Customer> lineMapper = new DefaultLineMapper<>();
		DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
		tokenizer.setNames("name", "age", "year");
        
		lineMapper.setLineTokenizer(tokenizer);
		BeanWrapperFieldSetMapper<Customer> fieldSetMapper 
        	= new BeanWrapperFieldSetMapper<>();
            
		fieldSetMapper.setTargetType(Customer.class);
		lineMapper.setFieldSetMapper(fieldSetMapper);
		itemReader.setLineMapper(lineMapper);
		
		itemReader.setLinesToSkip(1);
		return itemReader;
	}
	
	@Bean("FlatFileConfiguration-step-01")
	public Step step1() {
		return new StepBuilder("FlatFileConfiguration-step-01", jobRepository)
			.<Customer, Customer>chunk(5, platformTransactionManager)
			.reader(itemReader2())
			.writer(items -> {
				for (Customer item : items) {
					System.out.println("item = " + item);
				}
			})
			.build();
	}

	// Reader 작성법(2): builder 사용
	@Bean("FlatFileConfiguration-item-reader-02")
	public ItemReader<Customer> itemReader2() {
		return new FlatFileItemReaderBuilder<Customer>()
			.name("FlatFileConfiguration-item-reader-02")
			.resource(new ClassPathResource("customer.csv"))
			// .lineTokenizer(new DelimitedLineTokenizer())
			.delimited().delimiter(",").names("name", "age", "year")
			.fieldSetMapper(new BeanWrapperFieldSetMapper<>()).targetType(Customer.class)
			.linesToSkip(1)
			.build();
	}
}

참고: 사용한 Customer.java

public record Customer(String name, int age, String year) { }





XML

의존성 확인

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
  <groupId>com.thoughtworks.xstream</groupId>
  <artifactId>xstream</artifactId>
  <version>1.4.20</version>
</dependency>

테스트 파일: customer.xml

<?xml version="1.0" encoding="UTF-8" ?>
<customers>
    <customer id="1">
        <id>1</id>
        <name>홍길동</name>
        <age>40</age>
    </customer>
    <customer id="2">
        <id>2</id>
        <name>김말순</name>
        <age>41</age>
    </customer>
    <customer id="3">
        <id>3</id>
        <name>박찬호</name>
        <age>42</age>
    </customer>
</customers>

StaxEventItemReader 사용

package coding.toast.batch.xml_reader;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
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.Unmarshaller;
import org.springframework.oxm.xstream.XStreamMarshaller;
import org.springframework.transaction.PlatformTransactionManager;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Configuration
@RequiredArgsConstructor
public class XMLConfiguration {
	
	private final JobRepository jobRepository;
	private final PlatformTransactionManager platformTransactionManager;
	
	@Bean("XMLConfiguration-Job-01")
	public Job job() {
		return new JobBuilder("XMLConfiguration-Job-01", jobRepository)
			.start(step1()).build();
	}
	
	
	@Bean("XMLConfiguration-Step-01")
	public Step step1() {
		return new StepBuilder("XMLConfiguration-Step-01", jobRepository)
			.<Customer, Customer>chunk(5, platformTransactionManager)
			.reader(customItemReader())
			.writer(items -> {
				for (Customer item : items) {
					System.out.println("item = " + item);
				}
			})
			.build();
	}
	
	@Bean("XMLConfiguration-Reader-01")
	public ItemReader<Customer> customItemReader() {
		return new StaxEventItemReaderBuilder<Customer>()
			.name("XMLConfiguration-Reader-01")
			.resource(new ClassPathResource("customer.xml"))
			.addFragmentRootElements("customer")
			.unmarshaller(itemUnmarshaller())
			.build();
	}
	
	@Bean("XMLConfiguration-Unmarshaller-01")
	public Unmarshaller itemUnmarshaller() {
		Map<String, Class<?>> alias = new HashMap<>();
		alias.put("customer", Customer.class); // 항상 루트 명부터!
		alias.put("id", Long.class);
		alias.put("name", String.class);
		alias.put("age", Integer.class);
		
		XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
		xStreamMarshaller.setEncoding(StandardCharsets.UTF_8.toString());
		XStream xStream = xStreamMarshaller.getXStream();
		xStream.alias("customer", Customer.class);
		xStream.addPermission(AnyTypePermission.ANY);
		xStreamMarshaller.setAliases(alias);
		return xStreamMarshaller;
	}
	
}

참고: Customer.java 코드

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class Customer {
	Long id;
	String name;
	Integer age;
}





Json 읽기

의존성 확인

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>

테스트 파일: customer.json

[
  {
    "id": 1,
    "name": "홍길동",
    "age": 40
  },
  {
    "id": 2,
    "name": "박세리",
    "age": 41
  },
  {
    "id": 3,
    "name": "박찬호",
    "age": 42
  }
]

JsonItemReader 사용

package coding.toast.batch.json_reader;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.json.JacksonJsonObjectReader;
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;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@RequiredArgsConstructor
public class JsonConfiguration {
	
	private final JobRepository jobRepository;
	private final PlatformTransactionManager platformTransactionManager;
	
	@Bean("JsonConfiguration-Job-01")
	public Job job() {
		return new JobBuilder("JsonConfiguration-Job-01", jobRepository)
			.start(step1()).build();
	}
	
	
	@Bean("JsonConfiguration-Step-01")
	public Step step1() {
		return new StepBuilder("JsonConfiguration-Step-01", jobRepository)
			.<Customer, Customer>chunk(5, platformTransactionManager)
			.reader(customerItemReader())
			.writer(items -> {
				for (Customer item : items) {
					System.out.println("item = " + item);
				}
			})
			.build();
	}
	
	@Bean("JsonConfiguration-Reader-01")
	public ItemReader<Customer> customerItemReader() {
		return new JsonItemReaderBuilder<Customer>()
			.name("jsonReader")
			.resource(new ClassPathResource("customer.json"))
			.jsonObjectReader(new JacksonJsonObjectReader<>(Customer.class))
			.build();
	}
}

참고: Customer.java 코드

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class Customer {
	Long id;
	String name;
	Integer age;
}



보충: Excel 읽기

https://github.com/spring-projects/spring-batch-extensions/tree/main/spring-batch-excel 를 참고해주세요.
추후에 시간이 되면 예시를 작성해보겠습니다.

profile
백엔드 개발자로 일하고 있는 식빵(🍞)입니다.

0개의 댓글