บทช่วยสอน Spring Batch: การประมวลผลแบทช์ทำได้ง่ายด้วย Spring
เผยแพร่แล้ว: 2022-03-11การประมวลผลแบบกลุ่ม—จำแนกตามแบบเป็นกลุ่ม, ไม่โต้ตอบ และมักจะทำงานเป็นเวลานาน—ในเบื้องหลัง—ถูกใช้อย่างกว้างขวางในแทบทุกอุตสาหกรรมและถูกนำไปใช้กับงานที่หลากหลาย การประมวลผลแบบกลุ่มอาจเป็นข้อมูลหรือการประมวลผลแบบเข้มข้น ดำเนินการตามลำดับหรือแบบคู่ขนาน และอาจเริ่มต้นผ่านรูปแบบการเรียกใช้ต่างๆ รวมถึงเฉพาะกิจ กำหนดเวลา และตามความต้องการ
บทช่วยสอน Spring Batch นี้อธิบายรูปแบบการเขียนโปรแกรมและภาษาโดเมนของแอปพลิเคชันแบบแบตช์โดยทั่วไป และโดยเฉพาะอย่างยิ่ง แสดงแนวทางที่เป็นประโยชน์บางประการในการออกแบบและพัฒนาแอปพลิเคชันแบบแบตช์โดยใช้เวอร์ชัน Spring Batch 3.0.7 ปัจจุบัน
สปริงแบทช์คืออะไร?
Spring Batch เป็นเฟรมเวิร์กที่มีน้ำหนักเบาและครอบคลุมซึ่งออกแบบมาเพื่ออำนวยความสะดวกในการพัฒนาแอพพลิเคชั่นแบบแบตช์ที่มีประสิทธิภาพ นอกจากนี้ยังให้บริการด้านเทคนิคขั้นสูงและคุณสมบัติที่รองรับงานแบตช์ปริมาณมากและประสิทธิภาพสูง ผ่านการเพิ่มประสิทธิภาพและเทคนิคการแบ่งพาร์ติชั่น Spring Batch สร้างขึ้นจากแนวทางการพัฒนาตาม POJO ของ Spring Framework ซึ่งคุ้นเคยกับนักพัฒนา Spring ที่มีประสบการณ์ทุกคน
ตัวอย่างเช่น บทความนี้จะพิจารณาซอร์สโค้ดจากโครงการตัวอย่างที่โหลดไฟล์ลูกค้าที่จัดรูปแบบ XML กรองลูกค้าตามแอตทริบิวต์ต่างๆ และส่งออกรายการที่กรองไปยังไฟล์ข้อความ ซอร์สโค้ดสำหรับตัวอย่าง Spring Batch ของเรา (ซึ่งใช้คำอธิบายประกอบ Lombok) มีให้ที่นี่บน GitHub และต้องใช้ Java SE 8 และ Maven
การประมวลผลแบบแบตช์คืออะไร? แนวคิดหลักและคำศัพท์
เป็นสิ่งสำคัญที่ผู้พัฒนาชุดงานทุกคนจะต้องคุ้นเคยและคุ้นเคยกับแนวคิดหลักของการประมวลผลแบบกลุ่ม ไดอะแกรมด้านล่างเป็นเวอร์ชันที่เรียบง่ายของสถาปัตยกรรมอ้างอิงแบบกลุ่มที่ได้รับการพิสูจน์แล้วผ่านการใช้งานหลายทศวรรษบนแพลตฟอร์มต่างๆ โดยจะแนะนำแนวคิดหลักและข้อกำหนดที่เกี่ยวข้องกับการประมวลผลแบบแบตช์ ตามที่ใช้โดย Spring Batch
ดังที่แสดงในตัวอย่างการประมวลผลแบทช์ของเรา โดยทั่วไป กระบวนการแบทช์จะถูกห่อหุ้มโดย Job
ที่ประกอบด้วยหลาย Step
โดยทั่วไปแต่ละ Step
จะมี ItemReader
, ItemProcessor
และ ItemWriter
Job
ดำเนินการโดย JobLauncher
และข้อมูลเมตาเกี่ยวกับงานที่กำหนดค่าและดำเนินการจะถูกเก็บไว้ใน JobRepository
งานแต่ละ Job
อาจเชื่อมโยงกับ JobInstance
หลายรายการ ซึ่งแต่ละงานถูกกำหนดโดย JobParameters
เฉพาะเจาะจงซึ่งใช้เพื่อเริ่มงานแบบแบตช์ การเรียกใช้ JobInstance
แต่ละครั้งจะเรียกว่า JobExecution
โดยทั่วไป JobExecution
แต่ละรายการจะติดตามสิ่งที่เกิดขึ้นระหว่างการรัน เช่น สถานะปัจจุบันและสถานะออก เวลาเริ่มต้นและสิ้นสุด ฯลฯ
Step
เป็นขั้นตอนที่เป็นอิสระและเฉพาะเจาะจงของ Job
แบทช์ ซึ่งงานทุก Job
ประกอบด้วยขั้นตอนอย่างน้อยหนึ่ง Step
คล้ายกับ Job
Step
มี StepExecution
แต่ละรายการที่แสดงถึงความพยายามครั้งเดียวในการดำเนินการ Step
StepExecution
เก็บข้อมูลเกี่ยวกับสถานะปัจจุบันและสถานะออก เวลาเริ่มต้นและสิ้นสุด และอื่นๆ ตลอดจนการอ้างอิงถึงอินสแตนซ์ Step
และ JobExecution
ที่เกี่ยวข้อง
ExecutionContext
คือชุดของคู่คีย์-ค่าที่มีข้อมูลซึ่งกำหนดขอบเขตเป็น StepExecution
หรือ JobExecution
Spring Batch จะคง ExecutionContext
ไว้ ซึ่งจะช่วยในกรณีที่คุณต้องการรีสตาร์ทการรันแบบแบตช์ (เช่น เมื่อเกิดข้อผิดพลาดร้ายแรง เป็นต้น) ทั้งหมดที่จำเป็นคือการใส่วัตถุใด ๆ ที่จะแบ่งปันระหว่างขั้นตอนในบริบทและกรอบงานจะดูแลส่วนที่เหลือ หลังจากรีสตาร์ท ค่าจาก ExecutionContext
ก่อนหน้าจะถูกกู้คืนจากฐานข้อมูลและนำไปใช้
JobRepository
เป็นกลไกใน Spring Batch ที่ทำให้การคงอยู่ทั้งหมดนี้เป็นไปได้ มีการดำเนินการ CRUD สำหรับการสร้างอินสแตนซ์ JobLauncher
, Job
และ Step
เมื่อเรียกใช้ Job
JobExecution
จะได้รับจากที่เก็บ และระหว่างการดำเนินการ อินสแตนซ์ StepExecution
และ JobExecution
จะยังคงอยู่ในที่เก็บ
เริ่มต้นใช้งาน Spring Batch Framework
ข้อดีอย่างหนึ่งของ Spring Batch คือการพึ่งพาโปรเจ็กต์เพียงเล็กน้อย ซึ่งทำให้ง่ายต่อการเริ่มต้นและดำเนินการอย่างรวดเร็ว มีการระบุและอธิบายการพึ่งพาบางอย่างที่มีอยู่อย่างชัดเจนใน pom.xml
ของโปรเจ็กต์ ซึ่งสามารถเข้าถึงได้ที่นี่
การเริ่มต้นแอปพลิเคชันที่เกิดขึ้นจริงเกิดขึ้นในคลาสที่มีลักษณะดังนี้:
@EnableBatchProcessing @SpringBootApplication public class BatchApplication { public static void main(String[] args) { prepareTestData(1000); SpringApplication.run(BatchApplication.class, args); } }
คำอธิบายประกอบ @EnableBatchProcessing
เปิดใช้งานฟีเจอร์ Spring Batch และจัดเตรียมการกำหนดค่าพื้นฐานสำหรับการตั้งค่างานแบตช์
คำอธิบายประกอบ @SpringBootApplication
มาจากโปรเจ็กต์ Spring Boot ที่มีแอปพลิเคชันที่ทำงานบนสปริงแบบสแตนด์อโลน ระบุคลาสการกำหนดค่าที่ประกาศ Spring bean อย่างน้อยหนึ่งรายการ และยังทริกเกอร์การกำหนดค่าอัตโนมัติและการสแกนคอมโพเนนต์ของ Spring
โปรเจ็กต์ตัวอย่างของเรามีงานเดียวที่กำหนดค่าโดย CustomerReportJobConfig
ด้วย JobBuilderFactory
และ StepBuilderFactory
ที่ฉีดเข้าไป การกำหนดค่างานขั้นต่ำสามารถกำหนดได้ใน CustomerReportJobConfig
ดังต่อไปนี้:
@Configuration public class CustomerReportJobConfig { @Autowired private JobBuilderFactory jobBuilders; @Autowired private StepBuilderFactory stepBuilders; @Bean public Job customerReportJob() { return jobBuilders.get("customerReportJob") .start(taskletStep()) .next(chunkStep()) .build(); } @Bean public Step taskletStep() { return stepBuilders.get("taskletStep") .tasklet(tasklet()) .build(); } @Bean public Tasklet tasklet() { return (contribution, chunkContext) -> { return RepeatStatus.FINISHED; }; } }
มีสองวิธีหลักในการสร้างขั้นตอน
วิธีหนึ่ง ดังที่แสดงในตัวอย่างข้างต้น คือ tasklet-based Tasklet
รองรับอินเทอร์เฟซธรรมดาที่มีเพียงหนึ่งเมธอด, execute()
ซึ่งถูกเรียกซ้ำๆ จนกว่าจะส่งคืน RepeatStatus.FINISHED
หรือส่งข้อยกเว้นเพื่อส่งสัญญาณความล้มเหลว การเรียกไปยัง Tasklet
แต่ละครั้งจะรวมอยู่ในธุรกรรม
อีกวิธีหนึ่งคือ การประมวลผลแบบเน้น กลุ่ม หมายถึงการอ่านข้อมูลตามลำดับและสร้าง “ส่วนย่อย” ที่จะเขียนออกมาภายในขอบเขตของธุรกรรม แต่ละรายการจะถูกอ่านจาก ItemReader
ส่งมอบให้กับ ItemProcessor
และรวมเข้าด้วยกัน เมื่อจำนวนรายการที่อ่านเท่ากับช่วงคอมมิต ชิ้นส่วนทั้งหมดจะถูกเขียนผ่าน ItemWriter
จากนั้นธุรกรรมจะถูกคอมมิต ขั้นตอนเชิงกลุ่มสามารถกำหนดค่าได้ดังนี้:
@Bean public Job customerReportJob() { return jobBuilders.get("customerReportJob") .start(taskletStep()) .next(chunkStep()) .build(); } @Bean public Step chunkStep() { return stepBuilders.get("chunkStep") .<Customer, Customer>chunk(20) .reader(reader()) .processor(processor()) .writer(writer()) .build(); }
เมธอด chunk()
สร้างขั้นตอนที่ประมวลผลรายการเป็นชิ้น ๆ ด้วยขนาดที่มีให้ จากนั้นแต่ละอันจะถูกส่งไปยังผู้อ่าน ตัวประมวลผล และตัวเขียนที่ระบุ วิธีการเหล่านี้จะกล่าวถึงในรายละเอียดเพิ่มเติมในหัวข้อถัดไปของบทความนี้
ผู้อ่านที่กำหนดเอง
สำหรับแอปพลิเคชันตัวอย่าง Spring Batch ของเรา เพื่ออ่านรายชื่อลูกค้าจากไฟล์ XML เราจำเป็นต้องจัดเตรียมการใช้งานอินเทอร์เฟซ org.springframework.batch.item.ItemReader
:
public interface ItemReader<T> { T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException; }
ItemReader
ให้ข้อมูลและคาดว่าจะมีการเก็บสถานะ โดยทั่วไปจะมีการเรียกหลายครั้งสำหรับแต่ละชุดงาน โดยแต่ละครั้งการเรียกเพื่อ read()
จะคืนค่าถัดไปและสุดท้ายจะคืน null
เมื่อข้อมูลอินพุตทั้งหมดหมดลง
Spring Batch มีการใช้งาน ItemReader
ซึ่งสามารถใช้เพื่อวัตถุประสงค์ที่หลากหลาย เช่น การอ่านคอลเลกชั่น ไฟล์ การผสานรวม JMS และ JDBC ตลอดจนแหล่งข้อมูลต่างๆ เป็นต้น
ในแอปพลิเคชันตัวอย่างของเรา คลาส CustomerItemReader
มอบหมายการเรียก read()
จริงไปยังอินสแตนซ์ที่เริ่มต้นอย่างขี้เกียจของคลาส IteratorItemReader
:
public class CustomerItemReader implements ItemReader<Customer> { private final String filename; private ItemReader<Customer> delegate; public CustomerItemReader(final String filename) { this.filename = filename; } @Override public Customer read() throws Exception { if (delegate == null) { delegate = new IteratorItemReader<>(customers()); } return delegate.read(); } private List<Customer> customers() throws FileNotFoundException { try (XMLDecoder decoder = new XMLDecoder(new FileInputStream(filename))) { return (List<Customer>) decoder.readObject(); } } }
Spring bean สำหรับการใช้งานนี้ถูกสร้างขึ้นด้วยคำอธิบายประกอบ @Component
และ @StepScope
ทำให้ Spring ทราบว่าคลาสนี้เป็นคอมโพเนนต์ Spring แบบกำหนดขอบเขตขั้นตอน และจะถูกสร้างขึ้นหนึ่งครั้งต่อการดำเนินการตามขั้นตอนดังนี้:
@StepScope @Bean public ItemReader<Customer> reader() { return new CustomerItemReader(XML_FILE); }
โปรเซสเซอร์แบบกำหนดเอง
ItemProcessors
แปลงรายการอินพุตและแนะนำตรรกะทางธุรกิจในสถานการณ์การประมวลผลเชิงรายการ พวกเขาต้องจัดเตรียมการใช้งานอินเทอร์เฟซ org.springframework.batch.item.ItemProcessor
:
public interface ItemProcessor<I, O> { O process(I item) throws Exception; }
process()
ยอมรับอินสแตนซ์หนึ่งของคลาส I
และอาจส่งคืนอินสแตนซ์ประเภทเดียวกันหรือไม่ก็ได้ การคืน null
บ่งชี้ว่ารายการไม่ควรดำเนินการต่อไป ตามปกติแล้ว Spring มีตัวประมวลผลมาตรฐานไม่กี่ตัว เช่น CompositeItemProcessor
ที่ส่งผ่านรายการผ่านลำดับของ ItemProcessor
ที่ฉีดเข้าไป และ ValidatingItemProcessor
ที่ตรวจสอบความถูกต้องของอินพุต
ในกรณีของแอปพลิเคชันตัวอย่างของเรา โปรเซสเซอร์ถูกใช้เพื่อกรองลูกค้าตามข้อกำหนดต่อไปนี้:
- ลูกค้าต้องเกิดในเดือนปัจจุบัน (เช่น เพื่อตั้งค่าสถานะสำหรับวันเกิดพิเศษ ฯลฯ)
- ลูกค้าต้องมีธุรกรรมที่เสร็จสมบูรณ์น้อยกว่าห้ารายการ (เช่น เพื่อระบุลูกค้าใหม่)
ข้อกำหนด "เดือนปัจจุบัน" ดำเนินการผ่าน ItemProcessor
ที่กำหนดเอง:
public class BirthdayFilterProcessor implements ItemProcessor<Customer, Customer> { @Override public Customer process(final Customer item) throws Exception { if (new GregorianCalendar().get(Calendar.MONTH) == item.getBirthday().get(Calendar.MONTH)) { return item; } return null; } }
ข้อกำหนด "จำนวนจำกัดของธุรกรรม" ถูกนำไปใช้เป็น ValidatingItemProcessor
:

public class TransactionValidatingProcessor extends ValidatingItemProcessor<Customer> { public TransactionValidatingProcessor(final int limit) { super( item -> { if (item.getTransactions() >= limit) { throw new ValidationException("Customer has less than " + limit + " transactions"); } } ); setFilter(true); } }
โปรเซสเซอร์คู่นี้ถูกห่อหุ้มไว้ภายใน CompositeItemProcessor
ที่ใช้รูปแบบผู้รับมอบสิทธิ์:
@StepScope @Bean public ItemProcessor<Customer, Customer> processor() { final CompositeItemProcessor<Customer, Customer> processor = new CompositeItemProcessor<>(); processor.setDelegates(Arrays.asList(new BirthdayFilterProcessor(), new TransactionValidatingProcessor(5))); return processor; }
นักเขียนที่กำหนดเอง
สำหรับการส่งข้อมูลออก Spring Batch จัดเตรียมอินเทอร์เฟซ org.springframework.batch.item.ItemWriter
สำหรับการจัดลำดับวัตถุตามความจำเป็น:
public interface ItemWriter<T> { void write(List<? extends T> items) throws Exception; }
วิธี write()
มีหน้าที่ในการตรวจสอบให้แน่ใจว่าบัฟเฟอร์ภายในถูกล้าง หากธุรกรรมมีการใช้งาน มักจะจำเป็นต้องละทิ้งเอาต์พุตในการย้อนกลับครั้งต่อๆ ไป ทรัพยากรที่ผู้เขียนกำลังส่งข้อมูลโดยปกติควรจะสามารถจัดการสิ่งนี้ได้ มีการใช้งานมาตรฐานเช่น CompositeItemWriter
, JdbcBatchItemWriter
, JmsItemWriter
, JpaItemWriter
, SimpleMailMessageItemWriter
และอื่นๆ
ในแอปพลิเคชันตัวอย่างของเรา รายชื่อลูกค้าที่ผ่านการกรองจะถูกเขียนออกมาดังนี้:
public class CustomerItemWriter implements ItemWriter<Customer>, Closeable { private final PrintWriter writer; public CustomerItemWriter() { OutputStream out; try { out = new FileOutputStream("output.txt"); } catch (FileNotFoundException e) { out = System.out; } this.writer = new PrintWriter(out); } @Override public void write(final List<? extends Customer> items) throws Exception { for (Customer item : items) { writer.println(item.toString()); } } @PreDestroy @Override public void close() throws IOException { writer.close(); } }
การจัดตารางงาน Spring Batch
ตามค่าเริ่มต้น Spring Batch จะดำเนินการงานทั้งหมดที่ค้นหาได้ (เช่น ที่มีการกำหนดค่าใน CustomerReportJobConfig
) เมื่อเริ่มต้น หากต้องการเปลี่ยนลักษณะการทำงานนี้ ให้ปิดใช้งานการเรียกใช้งานเมื่อเริ่มต้นโดยเพิ่มคุณสมบัติต่อไปนี้ใน application.properties
:
spring.batch.job.enabled=false
จากนั้น การจัดกำหนดการจริงทำได้โดยการเพิ่มคำอธิบายประกอบ @EnableScheduling
ให้กับคลาสการกำหนดค่าและคำอธิบายประกอบ @Scheduled
ให้กับวิธีการที่ดำเนินการงานเอง การจัดกำหนดการสามารถกำหนดค่าได้ด้วยนิพจน์การหน่วงเวลา อัตรา หรือ cron:
// run every 5000 msec (ie, every 5 secs) @Scheduled(fixedRate = 5000) public void run() throws Exception { JobExecution execution = jobLauncher.run( customerReportJob(), new JobParametersBuilder().toJobParameters() ); }
มีปัญหากับตัวอย่างข้างต้นแม้ว่า เมื่อรันไทม์ งานจะสำเร็จในครั้งแรกเท่านั้น เมื่อเปิดตัวครั้งที่สอง (เช่น หลังจากห้าวินาที) จะสร้างข้อความต่อไปนี้ในบันทึก (โปรดทราบว่าใน Spring Batch เวอร์ชันก่อนหน้าจะมีการโยน JobInstanceAlreadyCompleteException
):
INFO 36988 --- [pool-2-thread-1] osbclsupport.SimpleJobLauncher : Job: [SimpleJob: [name=customerReportJob]] launched with the following parameters: [{}] INFO 36988 --- [pool-2-thread-1] osbatch.core.job.SimpleStepHandler : Step already complete or not restartable, so no action to execute: StepExecution: id=1, version=3, name=taskletStep, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription= INFO 36988 --- [pool-2-thread-1] osbatch.core.job.SimpleStepHandler : Step already complete or not restartable, so no action to execute: StepExecution: id=2, version=53, name=chunkStep, status=COMPLETED, exitStatus=COMPLETED, readCount=1000, filterCount=982, writeCount=18 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=51, rollbackCount=0, exitDescription=
สิ่งนี้เกิดขึ้นเพราะสามารถสร้างและดำเนินการได้เฉพาะ JobInstance
ที่ไม่ซ้ำกันเท่านั้น และ Spring Batch ไม่มีทางแยกความแตกต่างระหว่าง JobInstance
ตัวแรกและตัวที่สอง
มีสองวิธีในการหลีกเลี่ยงปัญหานี้เมื่อคุณจัดกำหนดการงานชุดงาน
หนึ่งคือต้องแน่ใจว่าได้แนะนำพารามิเตอร์ที่ไม่ซ้ำกันอย่างน้อยหนึ่งตัว (เช่น เวลาเริ่มต้นจริงในหน่วยนาโนวินาที) ให้กับแต่ละงาน:
@Scheduled(fixedRate = 5000) public void run() throws Exception { jobLauncher.run( customerReportJob(), new JobParametersBuilder().addLong("uniqueness", System.nanoTime()).toJobParameters() ); }
อีกวิธีหนึ่ง คุณสามารถเปิดงานถัดไปในลำดับของ JobInstance
ที่กำหนดโดย JobParametersIncrementer
ที่แนบกับงานที่ระบุด้วย SimpleJobOperator.startNextInstance()
:
@Autowired private JobOperator operator; @Autowired private JobExplorer jobs; @Scheduled(fixedRate = 5000) public void run() throws Exception { List<JobInstance> lastInstances = jobs.getJobInstances(JOB_NAME, 0, 1); if (lastInstances.isEmpty()) { jobLauncher.run(customerReportJob(), new JobParameters()); } else { operator.startNextInstance(JOB_NAME); } }
การทดสอบหน่วยสปริงแบทช์
โดยปกติ ในการรันการทดสอบหน่วยในแอปพลิเคชัน Spring Boot เฟรมเวิร์กจะต้องโหลด ApplicationContext
ที่สอดคล้องกัน มีการใช้คำอธิบายประกอบสองรายการเพื่อจุดประสงค์นี้:
@RunWith(SpringRunner.class) @ContextConfiguration(classes = {...})
มีคลาสยูทิลิตี้ org.springframework.batch.test.JobLauncherTestUtils
เพื่อทดสอบงานแบทช์ มีวิธีการในการเริ่มงานทั้งหมดรวมทั้งการทดสอบแบบ end-to-end ของแต่ละขั้นตอนโดยไม่ต้องรันทุกขั้นตอนในงาน ต้องประกาศเป็น Spring bean:
@Configuration public class BatchTestConfiguration { @Bean public JobLauncherTestUtils jobLauncherTestUtils() { return new JobLauncherTestUtils(); } }
การทดสอบทั่วไปสำหรับงานและขั้นตอนมีลักษณะดังนี้ (และสามารถใช้กรอบงานการเยาะเย้ยได้เช่นกัน):
@RunWith(SpringRunner.class) @ContextConfiguration(classes = {BatchApplication.class, BatchTestConfiguration.class}) public class CustomerReportJobConfigTest { @Autowired private JobLauncherTestUtils testUtils; @Autowired private CustomerReportJobConfig config; @Test public void testEntireJob() throws Exception { final JobExecution result = testUtils.getJobLauncher().run(config.customerReportJob(), testUtils.getUniqueJobParameters()); Assert.assertNotNull(result); Assert.assertEquals(BatchStatus.COMPLETED, result.getStatus()); } @Test public void testSpecificStep() { Assert.assertEquals(BatchStatus.COMPLETED, testUtils.launchStep("taskletStep").getStatus()); } }
Spring Batch นำเสนอขอบเขตเพิ่มเติมสำหรับบริบทขั้นตอนและงาน อ็อบเจ็กต์ในขอบเขตเหล่านี้ใช้ Spring container เป็นโรงงานอ็อบเจ็กต์ ดังนั้นจึงมีเพียงหนึ่งอินสแตนซ์ของแต่ละ bean ต่อขั้นตอนการดำเนินการหรืองาน นอกจากนี้ ยังมีการสนับสนุนสำหรับการเชื่อมโยงล่าช้าของการอ้างอิงที่สามารถเข้าถึงได้จาก StepContext
หรือ JobContext
ส่วนประกอบที่ได้รับการกำหนดค่าขณะรันไทม์ให้เป็นแบบขั้นตอนหรือแบบกำหนดขอบเขตงานนั้นยากต่อการทดสอบเป็นส่วนประกอบแบบสแตนด์อโลน เว้นแต่คุณจะมีวิธีตั้งค่าบริบทเหมือนกับว่าอยู่ในขั้นตอนหรือการดำเนินการงาน นั่นคือเป้าหมายของคอมโพเนนต์ org.springframework.batch.test.StepScopeTestExecutionListener
และ org.springframework.batch.test.StepScopeTestUtils
ใน Spring Batch ตลอดจน JobScopeTestExecutionListener
และ JobScopeTestUtils
TestExecutionListeners
ถูกประกาศที่ระดับคลาส และหน้าที่ของมันคือการสร้างบริบทการดำเนินการขั้นตอนสำหรับวิธีการทดสอบแต่ละวิธี ตัวอย่างเช่น:
@RunWith(SpringRunner.class) @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class}) @ContextConfiguration(classes = {BatchApplication.class, BatchTestConfiguration.class}) public class BirthdayFilterProcessorTest { @Autowired private BirthdayFilterProcessor processor; public StepExecution getStepExecution() { return MetaDataInstanceFactory.createStepExecution(); } @Test public void filter() throws Exception { final Customer customer = new Customer(); customer.setId(1); customer.setName("name"); customer.setBirthday(new GregorianCalendar()); Assert.assertNotNull(processor.process(customer)); } }
มี TestExecutionListener
สองรายการ หนึ่งมาจากเฟรมเวิร์ก Spring Test ปกติและจัดการการฉีดพึ่งพาจากบริบทของแอปพลิเคชันที่กำหนดค่าไว้ อีกอันคือ Spring Batch StepScopeTestExecutionListener
ที่ตั้งค่าบริบทขอบเขตขั้นตอนสำหรับการฉีดการพึ่งพาในการทดสอบหน่วย StepContext
ถูกสร้างขึ้นในช่วงระยะเวลาของวิธีการทดสอบและพร้อมใช้งานสำหรับการขึ้นต่อกันที่ถูกฉีดเข้าไป พฤติกรรมเริ่มต้นเป็นเพียงการสร้าง StepExecution
ที่มีคุณสมบัติคงที่ หรืออีกทางหนึ่ง กรณีทดสอบสามารถจัดเตรียม StepContext
เป็นวิธีการจากโรงงานที่ส่งคืนประเภทที่ถูกต้อง
อีกวิธีหนึ่งขึ้นอยู่กับคลาสยูทิลิตี้ StepScopeTestUtils
คลาสนี้ใช้เพื่อสร้างและจัดการ StepScope
ในการทดสอบหน่วยด้วยวิธีที่ยืดหยุ่นมากขึ้นโดยไม่ต้องใช้การพึ่งพาการฉีด ตัวอย่างเช่น การอ่าน ID ของลูกค้าที่กรองโดยโปรเซสเซอร์ด้านบนสามารถทำได้ดังนี้:
@Test public void filterId() throws Exception { final Customer customer = new Customer(); customer.setId(1); customer.setName("name"); customer.setBirthday(new GregorianCalendar()); final int id = StepScopeTestUtils.doInStepScope( getStepExecution(), () -> processor.process(customer).getId() ); Assert.assertEquals(1, id); }
พร้อมสำหรับ Advanced Spring Batch แล้วหรือยัง?
บทความนี้จะแนะนำพื้นฐานบางประการของการออกแบบและการพัฒนาแอปพลิเคชัน Spring Batch อย่างไรก็ตาม ยังมีหัวข้อและความสามารถขั้นสูงอีกมากมาย เช่น การปรับขนาด การประมวลผลแบบคู่ขนาน ผู้ฟัง และอื่นๆ ที่ไม่ได้กล่าวถึงในบทความนี้ หวังว่าบทความนี้จะเป็นพื้นฐานที่มีประโยชน์สำหรับการเริ่มต้นใช้งาน
ข้อมูลเกี่ยวกับหัวข้อขั้นสูงเหล่านี้สามารถพบได้ในเอกสาร Spring Back อย่างเป็นทางการสำหรับ Spring Batch