ItemProcessor

ItemPorcessor๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๊ฑฐ๋‚˜ ํ•„ํ„ฐ๋งํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋ฉฐ, ํ•„์ˆ˜๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ด ์—ญํ• ์€ ItemWriter์—์„œ๋„ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋ถ„๋ฆฌํ•จ์œผ๋กœ์จ ๋น„์ฆˆ๋‹ˆ์Šค ์ฝ”๋“œ๊ฐ€ ์„ž์ด๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

public interface ItemProcessor<I, O> {
    @Nullable
    O process(@NonNull I var1) throws Exception;
}

I๋Š” ItemReader์—์„œ ๋ฐ›์„ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ด๋ฉฐ, O๋Š” ItemWriter์— ๋ณด๋‚ผ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ด๋‹ค. ์ฆ‰, Reader์—์„œ ์ฝ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ItemProcessor์˜ process()๋ฅผ ํ†ต๊ณผํ•œ ํ›„ Writer์— ์ „๋‹ฌ๋œ๋‹ค. ๊ตฌํ˜„ํ•ด์•ผํ•  ๋ฉ”์†Œ๋“œ๋Š” processํ•˜๋‚˜์ด๋ฉฐ, Java 8๋ถ€ํ„ฐ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์˜ ์ถ”์ƒ ๋ฉ”์„œ๋“œ๊ฐ€ 1๊ฐœ์ธ ๊ฒฝ์šฐ ๋žŒ๋‹ค์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean(BEAN_PREFIX + "processor")
@StepScope
public ItemProcessor<ReadType, WriteType> processor() {
    return item -> {
        item.convert();
        return item;
    };
}
  • ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๊ฐ€ ์—†์–ด ๊ตฌํ˜„ ์ฝ”๋“œ ์–‘์ด ์ ๋‹ค. (๋น ๋ฅด๊ฒŒ ๊ตฌํ˜„ ๊ฐ€๋Šฅ)

  • ๊ณ ์ •๋œ ํ˜•ํƒœ๊ฐ€ ์—†์–ด ์›ํ•˜๋Š” ํ˜•ํƒœ์˜ ์–ด๋–ค ์ฒ˜๋ฆฌ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

  • Batch Config ํด๋ž˜์Šค ์•ˆ์— ํฌํ•จ๋˜์–ด ์žˆ์–ด์•ผ๋งŒ ํ•˜๋ฉฐ, Batch Config ์ฝ”๋“œ ์–‘์ด ๋งŽ์•„์งˆ ์ˆ˜ ์žˆ๋‹ค.

    • ์ฝ”๋“œ ์–‘์ด ๋งŽ์•„์ง€๋งŒ ๋ณ„๋„ ํด๋ž˜์Šค๋กœ Processor๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

ํฌ๊ฒŒ ItemProcessor๋Š” ๋‹ค์Œ ์—ญํ• ์„ ํ•œ๋‹ค.

  • ๋ณ€ํ™˜ : Reader์—์„œ ์ฝ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ, Writer์— ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋‹ค.

    		// Pay -> String ํƒ€์ž… ๋ณ€ํ™˜
    		@Bean
        public ItemProcessor<Pay, String> processor(){
            return pay -> {
                return pay.getTxName();
            };
        }
  • ํ•„ํ„ฐ : Reader์—์„œ ๋„˜๊ฒจ์ค€ ๋ฐ์ดํ„ฐ๋ฅผ Writer๋กœ ๋„˜๊ฒจ์ค„ ๊ฒƒ์ธ์ง€ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, null์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด Writer์— ์ „๋‹ฌ๋˜์ง€ ์•Š๋Š”๋‹ค.

    		// amount๊ฐ€ 10000์ด์ƒ์ธ ๊ฐ’๋งŒ Writer์— ๋„˜์–ด๊ฐ€๋„๋ก ํ•„ํ„ฐ
    		@Bean
        public ItemProcessor<Pay, Pay> nullProcessor(){
            return pay -> {
                if(pay.getAmount() < 10000){
                    log.info("Pay amount :{}", pay.getAmount());
                    return null;
                }
                return pay;
            };
        }

ItemProcessor๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ•ด๋‹น Item์˜ ๋ชจ๋“  ์ดํ›„ ์ฒ˜๋ฆฌ๊ฐ€ ์ค‘์ง€๋œ๋‹ค. ์ด๋•Œ null์„ ๋ฐ˜ํ™˜ํ•˜๋”๋ผ๋„ ๋‹ค๋ฅธ Item ์ฒ˜๋ฆฌ๊ฐ€ ๊ณ„์† ์ด๋ฃจ์–ด์ง„๋‹ค.

๊ตฌํ˜„์ฒด

Spring Batch ์—์„œ๋Š” ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์šฉ๋„์˜ Processor๋ฅผ ๋ฏธ๋ฆฌ ํด๋ž˜์Šค๋กœ ๋งŒ๋“ค์–ด์„œ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ๋‹ค.

  • ItemProcessorAdapter

  • ValidatingItemProcessor

  • CompositeItemProcessor

ํ•˜์ง€๋งŒ ์ตœ๊ทผ์—๋Š” ๋Œ€๋ถ€๋ถ„ processor๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ณ , ๋žŒ๋‹ค์‹์œผ๋กœ ๋น ๋ฅด๊ฒŒ ๊ตฌํ˜„ํ• ๋•Œ๋„ ๋งŽ๋‹ค. ๊ทธ๋ž˜์„œ ItemProcessorAdapter์™€ ValidatingItemProcessor๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ž…๋ ฅ ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์— ์‚ฌ์šฉํ•˜๋Š” ItemProcessor ๊ตฌํ˜„์ฒด์ด๋‹ค. ์ž…๋ ฅ ์•„์ดํ…œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์Šคํ”„๋ง๋ฐฐ์น˜ Validator๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ์‹คํŒจํ•˜๋ฉด, ValidationException์ด ๋ฐœ์ƒํ•œ๋‹ค.

  • org.springframework.batch.item.validator.ValidatingItemProcessor

BeanValidatingItemProcessor

JSR 303์€ ๋นˆ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•œ ๊ฒƒ์œผ๋กœ, ์Šคํ”„๋ง ๋ฐฐ์น˜๋Š” ๋ฏธ๋ฆฌ ์ •์˜๋œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๊ธฐ๋Šฅ์„ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ œ๊ณตํ•ด์ค€๋‹ค. ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ๋‹ค์Œ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผํ•œ๋‹ค.

implementation 'org.springframework.boot:spring-boot-starter-validation'
์–ด๋…ธํ…Œ์ด์…˜
์†์„ฑ
์„ค๋ช…

@NotNull @Null

๊ฐ’์ด null์ธ์ง€ ์•„๋‹Œ์ง€ ๊ฒ€์‚ฌ

@Size

int min : ์ตœ์†Œ ํฌ๊ธฐ(default : 0) int max : ์ตœ๋Œ€ํฌ๊ธฐ

๊ธธ์ด๋‚˜ ํฌ๊ธฐ๊ฐ€ ์ง€์ •ํ•œ ๊ฐ’ ๋ฒ”์œ„์— ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค๊ณ  ํŒ๋‹จ

@Pattern

String regexp = ์ •๊ทœํ‘œํ˜„์‹

๊ฐ’์ด ์ •๊ทœ ํ‘œํ˜„์‹์— ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค๊ณ  ํŒ๋‹จ

@AssertTrue @AssertFalse

๊ฐ’์ด true์ธ์ง€ false์ธ์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@DecmialMax @DecimalMin

String value: ์ตœ๋Œ€๊ฐ’ ๋˜๋Š” ์ตœ์†Ÿ๊ฐ’ boolean inclusive : ์ง€์ •๊ฐ’ ํฌํ•จ ์—ฌ๋ถ€(default : true)

์ง€์ •ํ•œ ๊ฐ’๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ˜น์€ ํฌ๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@Max @Min

long value

์ง€์ •ํ•œ ๊ฐ’๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ˜น์€ ํฌ๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@Digits

int integer : ํ—ˆ์šฉ๊ฐ€๋Šฅํ•œ ์ •์ˆ˜ ์ž๋ฆฟ์ˆ˜ int fraction : ํ—ˆ์šฉ ๊ฐ€๋Šฅํ•œ ์†Œ์ˆ˜์  ์ดํ•˜ ์ž๋ฆฟ์ˆ˜

์ž๋ฆฟ์ˆ˜๊ฐ€ ์ง€์ •ํ•œ ํฌ๊ธฐ๋ฅผ ๋„˜์ง€ ์•Š๋Š”์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@NotEmpty

๋ฌธ์ž์—ด, ๋ฐฐ์—ด : null์ด ์•„๋‹ˆ๊ณ , ๊ธธ์ด๊ฐ€ 0์ด ์•„๋‹Œ์ง€ ๊ฒ€์‚ฌ Collection : null์ด ์•„๋‹ˆ๊ณ , ํฌ๊ธฐ๊ฐ€ 0์ด ์•„๋‹Œ์ง€ ๊ฒ€์‚ฌ

@NotBlank

null์ด ์•„๋‹ˆ๊ณ  ์ตœ์†Œํ•œ ํ•œ๊ฐœ ์ด์ƒ์˜ ๊ณต๋ฐฑ์ด ์•„๋‹Œ ๋ฌธ์ž๋ฅผ ํฌํ•จํ•˜๋Š”์ง€ ๊ฒ€์‚ฌ

@Positive @PositiveOrZero

์–‘์ˆ˜์ธ์ง€ ๊ฒ€์‚ฌ OrZero๋Š” ์–‘์ˆ˜ ํ˜น์€ 0์ธ์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@Negative @NegativeOrZero

์Œ์ˆ˜์ธ์ง€ ๊ฒ€์‚ฌ OrZero๋Š” ์Œ์ˆ˜ ํ˜น์€ 0์ธ์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@Email

์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@Future @FuterOrPresent

ํ•ด๋‹น ์‹œ๊ฐ„์ด ๋ฏธ๋ž˜์ธ์ง€ ๊ฒ€์‚ฌ OrPresent๋Š” ํ˜„์žฌ ๋˜๋Š” ๋ฏธ๋ž˜์‹œ๊ฐ„์ธ์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

@Past @PastOrPresent

ํ•ด๋‹น ์‹œ๊ฐ„์ด ๊ณผ๊ฑฐ์ธ์ง€ ๊ฒ€์‚ฌ OrPresent๋Š” ํ˜„์žฌ ๋˜๋Š” ๊ณผ๊ฑฐ์‹œ๊ฐ„์ธ์ง€ ๊ฒ€์‚ฌ null์€ ์œ ํšจํ•˜๋‹ค ํŒ๋‹จ

public class Customer {

    @NotNull(message = "firstname์€ ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    @Pattern(regexp = "[a-zA-Z]+", message = "firstname์€ ์˜์–ด์—ฌ์•ผํ•ฉ๋‹ˆ๋‹ค.")
    private String firstName;

    @NotNull(message = "city ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    @Pattern(regexp = "[a-zA-Z\\. ]+")
    private String city;

    @NotNull(message = "state ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    @Size(min=2, max=2)
    @Pattern(regexp = "[A-Z{2}]+")
    private String state;

  // ...
}

์œ„ ์˜ˆ์ œ์™€ ๊ฐ™์ด ๊ณ ์œ ํ•œ ๋ฉ”์„ธ์ง€๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•„๋“œ ๊ฐ’์˜ ๊ธธ์ด๊ฐ€ ์ž˜๋ชป๋๋Š”์ง€ ํ˜•์‹์ด ์ž˜๋ชป๋๋Š”์ง€ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

    @Bean
    public Step validationDelimitedFileStep() {
        return this.stepBuilderFactory.get("validationDelimitedFileStep")
                .<Customer, Customer>chunk(10)
                .reader(validationDelimitedCustomerItemReader(null))
                .processor(validationCustomerProcessor()) // processor
                .writer(validationDelimitedCustomerItemWriter())
                .build();
    }

    /**
     * BeanValidationItemProcessor ์„ค์ •
     * @return
     */
    @Bean
    public BeanValidatingItemProcessor<Customer> validationCustomerProcessor() {
        return new BeanValidatingItemProcessor<>();
    }
Field error in object 'item' on field 'middleInitial': rejected value [YS]; codes [Size.item.middleInitial,Size.middleInitial,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [item.middleInitial,middleInitial]; arguments []; default message [middleInitial],1,1]; default message [ํฌ๊ธฐ๊ฐ€ 1์—์„œ 1 ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค]
Field error in object 'item' on field 'middleInitial': rejected value [YS]; codes [Pattern.item.middleInitial,Pattern.middleInitial,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [item.middleInitial,middleInitial]; arguments []; default message [middleInitial],[Ljavax.validation.constraints.Pattern$Flag;@5c7a06ec,[a-zA-Z]]; default message [middleInitial๋Š” ๋ฐ˜๋“œ์‹œ ์˜์–ด์—ฌ์•ผํ•ฉ๋‹ˆ๋‹ค.]

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง€์ •ํ•œ validation์— ๋งž์ง€ ์•Š์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

ValidatingItemProcessor

๋ฐ์ดํ„ฐ์…‹ ๋‚ด์—์„œ ํ•œ๊ฐœ์˜ ํ•„๋“œ์˜ ๊ฐ’์ด ๊ณ ์œ ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ณ ์œ ํ•œ ๊ฐ’์˜ ํ•„๋“œ๋ฅผ ItemStream ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ, ๊ฐ ์ปค๋ฐ‹๊ณผ ํ•„๋“œ ๊ฐ’์„ ExecutionContext์— ์ €์žฅํ•ด ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

/**
 * JobExecution ๊ฐ„์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ItemStreamSupport ์ƒ์†
 */
public class UniqueLastNameValidator extends ItemStreamSupport implements Validator<Customer> {

    private Set<String> lastNames = new HashSet<>();

    @Override
    public void validate(Customer value) throws ValidationException {
        if (lastNames.contains(value.getLastName())) {
            throw new ValidationException(value.getLastName() + " lastName์ด ์ค‘๋ณต๋ฉ๋‹ˆ๋‹ค.");
        }
        this.lastNames.add(value.getLastName());
    }

    @Override
    public void open(ExecutionContext executionContext) {

        String lastNames = getExecutionContextKey("lastNames");

        // lastNames๊ฐ€ Execution์— ์ €์žฅ๋˜์–ด์žˆ๋Š”์ง€ ํ™•์ธ ํ›„ ์ €์žฅ๋˜์–ด์žˆ๋‹ค๋ฉด, ์Šคํ… ์ฒ˜๋ฆฌ ์ด์ „์— ํ•ด๋‹น๊ฐ’์œผ๋กœ ์›๋ณต
        if (executionContext.containsKey(lastNames)) {
            this.lastNames = (Set<String>) executionContext.get(lastNames);
        }
    }

    /**
     * ์ฒญํฌ ๋‹จ์œ„๋กœ ์ˆ˜ํ–‰๋˜๋Š”๋ฐ, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ExecutionContext์— ์ €์žฅ
     * @param executionContext
     */
    @Override
    public void update(ExecutionContext executionContext) {
        Iterator<String> itr = lastNames.iterator();
        Set<String> copiedLastNames = new HashSet<>();

        while (itr.hasNext()) {
            copiedLastNames.add(itr.next());
        }

        executionContext.put(getExecutionContextKey("lastNames"), copiedLastNames);
    }
}

Validator๋ฅผ ๊ตฌํ˜„ํ•œ ํ›„ Step์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

@Bean
    public Step validationDelimitedFileStep() {
        return this.stepBuilderFactory.get("validationDelimitedFileStep")
                .<Customer, Customer>chunk(10)
                .reader(validationDelimitedCustomerItemReader(null))
                .processor(customerValidatingItemProcessor()) // ํ”„๋กœ์„ธ์„œ
                .writer(validationDelimitedCustomerItemWriter())
                .stream(uniqueLastNameValidator()) // stream ์„ค์ •
                .build();
    }



    @Bean
    public ValidatingItemProcessor<Customer> customerValidatingItemProcessor() {
        return new ValidatingItemProcessor<>(uniqueLastNameValidator());
    }

    @Bean
    public UniqueLastNameValidator uniqueLastNameValidator() {
        UniqueLastNameValidator uniqueLastNameValidator = new UniqueLastNameValidator();

        uniqueLastNameValidator.setName("uniqueLastNameValidator");

        return uniqueLastNameValidator;
    }

ItemProcessorAdapter

  • org.springframework.batch.item.adapter.ItemProcessorAdapter

์„œ๋น„์Šค๋ฅผ ItemProcessor ์—ญํ• ์„ ํ•˜๋„๋ก ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

@Service
public class UpperCaseNameService {
    public Customer upperCase(Customer customer) {

        Customer newCustomer = new Customer(customer);

        newCustomer.setFirstName(newCustomer.getFirstName().toUpperCase());
        newCustomer.setLastName(newCustomer.getLastName().toUpperCase());
        newCustomer.setMiddleInitial(newCustomer.getMiddleInitial().toUpperCase());

        return newCustomer;
    }
}

๊ณ ๊ฐ์˜ ์ด๋ฆ„์„ ๋Œ€๋ฌธ์ž๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ์„œ๋น„์Šค์ด๋‹ค. ์ด ์„œ๋น„์Šค๋ฅผ ItemProcessorAdapter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Processor๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean
public Step validationDelimitedFileStep() {
  	return this.stepBuilderFactory.get("validationDelimitedFileStep")
                .<Customer, Customer>chunk(10)
                .reader(validationDelimitedCustomerItemReader(null))
                .processor(customerItemProcessorAdapter())
                .writer(validationDelimitedCustomerItemWriter())
                .stream(uniqueLastNameValidator())
                .build();
}


@Bean
public ItemProcessorAdapter<Customer, Customer> customerItemProcessorAdapter() {
		ItemProcessorAdapter<Customer, Customer> adapter = new ItemProcessorAdapter<>();
    adapter.setTargetObject(upperCaseNameService);
    adapter.setTargetMethod("upperCase");
    return adapter;
}

ScriptItemProcessor

Ruby, JavaScript, Groovy ๋“ฑ ๋‹ค์–‘ํ•œ ์Šคํฌ๋ฆฝํŠธ ์–ธ์–ด๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • org.springframework.batch.item.support.ScriptItemProcessor

// lowerCase.js
item.setFirstName(item.getFirstName().toLowerCase());
item;
@Bean
@StepScope
public ScriptItemProcessor<Customer, Customer> scriptItemProcessor(@Value("#{jobParameters['script']}") Resource script) {
  	ScriptItemProcessor<Customer, Customer> itemProcessor = new ScriptItemProcessor<>();

    itemProcessor.setScript(script);

    return itemProcessor;
}

์ˆ˜ํ–‰ํ•˜๊ณ  ์‹ถ์€ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ”์ธ๋”ฉํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

CompositeItemProcessor

CompositeItemProcessor๋Š” ItemProcessor๊ฐ„์˜ ์ฒด์ด๋‹์„ ์ง€์›ํ•˜๋Š” Processor์ด๋‹ค.

    @Bean
    public CompositeItemProcessor compositeItemProcessor(){
        List<ItemProcessor> delegates = new ArrayList<>(2);
        delegates.add(nullProcessor());
        delegates.add(processor());

        CompositeItemProcessor processor = new CompositeItemProcessor();
        processor.setDelegates(delegates);
        return processor;
    }

		@Bean
    public ItemProcessor<Pay, String> processor(){
        return pay -> {
            return pay.getTxName();
        };
    }


    @Bean
    public ItemProcessor<Pay, Pay> nullProcessor(){
        return pay -> {
            if(pay.getAmount() < 10000){
                log.info("Pay amount :{}", pay.getAmount());
                return null;
            }
            return pay;
        };
    }

๋‹ค์Œ๊ณผ ๊ฐ™์ด Processor๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ฒด์ด๋‹ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ, ์—ฌ๊ธฐ์„œ ์ œ๋„ค๋ฆญ ํƒ€์ž…์€ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋ฉฐ, ๋งŒ์•ฝ ์ œ๋„ค๋ฆญํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด delegates์— ํฌํ•จ๋œ ItemProcessor๋Š” ๋ชจ๋‘ ๊ฐ™์€ ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ๊ฐ€์ ธ์•ผํ•œ๋‹ค. ๋งŒ์•ฝ ๊ฐ™์€ ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ItemProcessor๊ฐ„ ์ฒด์ด๋‹์ด๋ผ๋ฉด ์ œ๋„ค๋ฆญ์„ ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ „ํ•œ ์ฝ”๋“œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

ClassifierCompositeItemProcessor

  • org.springframework.batch.item.support.ClassifierCompositeItemProcessor

Classifier ๊ตฌํ˜„์ฒด๋กœ ์‚ฌ์šฉํ•  ItemProcessor๋ฅผ ์„ ์ •ํ•ด classify ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•ด ๋ถ„๋ฅ˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

public interface Classifier<C, T> extends Serializable {

	T classify(C classifiable);

}

๋‹ค์Œ์€ ์šฐํŽธ๋ฒˆํ˜ธ๋ฅผ ์ง์ˆ˜ ํ™€์ˆ˜๋กœ ๋ถ„๋ฅ˜ํ•œ Classifier ๊ตฌํ˜„์ฒด์ด๋‹ค.

@AllArgsConstructor
public class ZipCodeClassifier implements Classifier<Customer, ItemProcessor<Customer, Customer>> {

    private ItemProcessor<Customer, Customer> oddProcessor;
    private ItemProcessor<Customer, Customer> evenProcessor;


    @Override
    public ItemProcessor<Customer, Customer> classify(Customer classifiable) {
        if (Integer.parseInt(classifiable.getZipCode()) % 2 == 0) {
            return evenProcessor;
        } else {
            return oddProcessor;
        }
    }
}

๊ตฌํ˜„ํ•œ Classifier๋ฅผ ClassifierCompositeItemProcessor๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

		// ํ™€์ˆ˜, ์ง์ˆ˜ ํ”„๋กœ์„ธ์„œ ์„ค์ •
		@Bean
    public Classifier classifier() {
        return new ZipCodeClassifier(customerItemProcessorAdapter(), scriptItemProcessor());
    }

    @Bean
    public ClassifierCompositeItemProcessor<Customer, Customer> classifierCompositeItemProcessor() {
        ClassifierCompositeItemProcessor<Customer, Customer> itemProcessor = new ClassifierCompositeItemProcessor<>();
        itemProcessor.setClassifier(classifier());
        return itemProcessor;
    }

ํ™€์ˆ˜์ธ ๊ฒฝ์šฐ์—๋Š” upperCase์ถœ๋ ฅ, ์ง์ˆ˜์ธ ๊ฒฝ์šฐ lowerCase๋กœ ๋ถ„๋ฅ˜ํ•ด์„œ ์ถœ๋ ฅํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€์œผ๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์ƒ์ ์œผ๋กœ ์ถœ๋ ฅ๋˜๋Š”๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Customer(firstName=aimee, middleInitial=C, lastName=Hoover, addressNumber=7341, street=Vel Avenue, city=Mobile, state=AL, zipCode=35928, address=null, transactions=null)
Customer(firstName=JONAS, middleInitial=U, lastName=GILBERT, addressNumber=8852, street=In St., city=Saint Paul, state=MN, zipCode=57321, address=null, transactions=null)
Customer(firstName=REGAN, middleInitial=M, lastName=BAXTER, addressNumber=4851, street=Nec Av., city=Gulfport, state=MS, zipCode=33193, address=null, transactions=null)
Customer(firstName=OCTAVIUS, middleInitial=T, lastName=DAUGHERTY, addressNumber=7418, street=Cum Road, city=Houston, state=TX, zipCode=51507, address=null, transactions=null)
Customer(firstName=stuart, middleInitial=K, lastName=Mckenzie, addressNumber=5529, street=Orci Av., city=Nampa, state=ID, zipCode=18562, address=null, transactions=null)
Customer(firstName=PETRA, middleInitial=Z, lastName=LARA, addressNumber=8401, street=Et St., city=Georgia, state=GA, zipCode=70323, address=null, transactions=null)
Customer(firstName=cherokee, middleInitial=T, lastName=Laradd, addressNumber=8516, street=Mauris St., city=Seattle, state=WA, zipCode=28720, address=null, transactions=null)
Customer(firstName=athena, middleInitial=Y, lastName=Burt, addressNumber=4951, street=Mollis Rd., city=Newark, state=DE, zipCode=41034, address=null, transactions=null)
Customer(firstName=kaitlin, middleInitial=M, lastName=Macias, addressNumber=5715, street=Velit St., city=Chandler, state=AZ, zipCode=86176, address=null, transactions=null)
Customer(firstName=LEROY, middleInitial=X, lastName=CHERRY, addressNumber=7810, street=Vulputate St., city=Seattle, state=WA, zipCode=37703, address=null, transactions=null)

ItemProcessor ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ธฐ

์ปค์Šคํ…€ ํ”„๋กœ์„ธ์„œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ง์ˆ˜ ์šฐํŽธ๋ฒˆํ˜ธ๋Š” ํ•„ํ„ฐ๋งํ•˜๊ณ  ํ™€์ˆ˜ ์šฐํŽธ๋ฒˆํ˜ธ๋งŒ ๋‚จ๊ฒจ๋‘๋Š” ItemProcessor ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณผ ๊ฒƒ์ด๋‹ค.

ItemProcessor๋Š” **null**์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ•ด๋‹น ์•„์ดํ…œ์€ ๊ทธ ์ดํ›„ ์ˆ˜ํ–‰๋˜๋Š” ItemProcessor๋‚˜ ItemWriter๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š๊ณ , ํ•„ํ„ฐ๋ง๋œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•„ํ„ฐ๋ง๋œ ๋ ˆ์ฝ”๋“œ์˜ ์ˆ˜๋ฅผ JobRepository์— ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋‹ค.

public class EvenFilteringItemProcessor implements ItemProcessor<Customer, Customer> {
    @Override
    public Customer process(Customer item) throws Exception {
        return Integer.parseInt(item.getZipCode()) % 2 == 0 ? null : item;
    }
}

๋‹ค์Œ๊ณผ ๊ฐ™์ด ItemProcessor๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ, process ๋ฉ”์„œ๋“œ์— ์›ํ•˜๋Š” ๋กœ์ง์„ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

SELECT	STEP_EXECUTION_ID AS ID, STEP_NAME, COMMIT_COUNT, READ_COUNT, FILTER_COUNT, WRITE_COUNT
FROM		BATCH_STEP_EXECUTION;

BATCH_STEP_EXECUTION ๋‚ด์— ์„ฑ๊ณตํ•œ ์ˆ˜, ํ•„ํ„ฐ๋ง ๊ฑธ๋ฆฐ ์•„์ดํ…œ ์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฐธ๊ณ 

Last updated