There is always a healthy debate when talking Java and batches. When I heard Spring Batch, I had to try it out. On a previous project, many eons back, I did some batch processing in Java. What hurt me there (after a lots of optimizations) was a call to another persons module. His module happily loaded up an entity bean. You can guess where that ended. Next release I went through the code and replaced the entity bean calls with ONE update SQL statement. That fixed things. I was processing 200k records in 15-20 minutes, with an extremely small memory footprint. Even this I could reduce further if I tuned another module. But the performance was deemed enough and we moved on.
What I personally felt from that experience was the need of a decent Java-based Batch processing framework. Of course having this does not mean use Java for batches. Sometimes for bulk processing doing it in the database may be the right approach.
In this blog I want to go over Spring Batch processing. We will start off with some definitions.
Job – A job represents your entire batch work. Each night you need to collect all of the 1)credit card transactions, 2)collect them in a file and then 3)send them over to the settlement provider. Here I defined three logical steps. In Spring Batch a job is made of up of Steps. Each Step being a unit of work.
Step – A job is made up of one or more steps.
JobInstance – A running instance of the job that you have defined. Think of the Job as a class and the job instance as your , well object. Our credit card processing job runs 7 days a week at 11pm. Each executions is a JobInstance.
JobParameters – Parameters that go into a JobInstance.
JobExecution – Every attempt to run a JobInstance results in a JobExecution. For some reasons Jan 1st, 2008 CC Settlement job failed. It is re-run and now it succeeds. So we have one JobInstance but two executions (thus two JobExecutions). There also exists the concept of StepExecution. This represents an attempt to run a Step in a Job.
JobRepository – This is the persistent store for all of our job definitions. In this example I setup the repository to use an in-memory persistent store. You can back it up with a database if you want.
JobLauncher – As the name suggests, this object lets you launch a job.
TaskLet – Situations where you do not have input and output processing (using readers and writers). We use a tasklet in this blog.
The next three definitions do not apply to this blog since I will not be using them. Part II of this blog will show an example on these.
ItemReader – Abstraction used to represent an object that allows you to read in one object of interest that you want to process. In my credit card example it could be one card transaction retrieved from the database.
ItemWriter – Abstraction used to write out the final results of a batch. In the credit card example it could be a provider specific representation of the transaction which needs to be in a file. Maybe in XML or comma separated flat file.
ItemProcessor – Very important. Here you can initiate business logic on a just read item. Perform computations on the object and maybe calculate more fields before passing on to the writer to write out to the output file.
In this blog lets go through the age-old hell world example. Our job will run a task which prints out hello world. Not much happening here but will show all of the important concepts in work before Part-II where I use the reader and writer to read from a flat file and insert 200k records into the database (in about 1 minute). Wanted to throw that out to the naysayers who just hate doing batches in Java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
<span style="color: #507090"><?xml version="1.0" encoding="UTF-8"?></span> <span style="color: #007000"><beans</span> <span style="color: #0000C0">xmlns=</span><span style="background-color: #fff0f0">"http://www.springframework.org/schema/beans"</span> <span style="color: #0000C0">xmlns:xsi=</span><span style="background-color: #fff0f0">"http://www.w3.org/2001/XMLSchema-instance"</span> <span style="color: #0000C0">xmlns:aop=</span><span style="background-color: #fff0f0">"http://www.springframework.org/schema/aop"</span> <span style="color: #0000C0">xmlns:tx=</span><span style="background-color: #fff0f0">"http://www.springframework.org/schema/tx"</span> <span style="color: #0000C0">xmlns:batch=</span><span style="background-color: #fff0f0">"http://www.springframework.org/schema/batch"</span> <span style="color: #0000C0">xmlns:context=</span><span style="background-color: #fff0f0">"http://www.springframework.org/schema/context"</span> <span style="color: #0000C0">xsi:schemaLocation=</span><span style="background-color: #fff0f0">"</span> <span style="background-color: #fff0f0"> http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd</span> <span style="background-color: #fff0f0"> http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd</span> <span style="background-color: #fff0f0"> http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd</span> <span style="background-color: #fff0f0"> http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd</span> <span style="background-color: #fff0f0"> http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"</span><span style="color: #007000">></span> <span style="color: #808080"><!-- 1) USE ANNOTATIONS TO IDENTIFY AND WIRE SPRING BEANS. --></span> <span style="color: #007000"><context:component-scan</span> <span style="color: #0000C0">base-package=</span><span style="background-color: #fff0f0">"com.batch"</span> <span style="color: #007000">/></span> <span style="color: #808080"><!-- 2) DATASOURCE, TRANSACTION MANAGER AND JDBC TEMPLATE --></span> <span style="color: #007000"><bean</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"dataSource"</span> <span style="color: #0000C0">class=</span><span style="background-color: #fff0f0">"org.springframework.jdbc.datasource.DriverManagerDataSource"</span><span style="color: #007000">></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"driverClassName"</span> <span style="color: #0000C0">value=</span><span style="background-color: #fff0f0">"org.hsqldb.jdbcDriver"</span> <span style="color: #007000">/></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"url"</span> <span style="color: #0000C0">value=</span><span style="background-color: #fff0f0">"jdbc:hsqldb:file:target/data/ledger"</span> <span style="color: #007000">/></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"username"</span> <span style="color: #0000C0">value=</span><span style="background-color: #fff0f0">"SA"</span> <span style="color: #007000">/></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"password"</span> <span style="color: #0000C0">value=</span><span style="background-color: #fff0f0">""</span> <span style="color: #007000">/></span> <span style="color: #007000"></bean></span> <span style="color: #007000"><bean</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"transactionManager"</span> <span style="color: #0000C0">class=</span><span style="background-color: #fff0f0">"org.springframework.batch.support.transaction.ResourcelessTransactionManager"</span><span style="color: #007000">></span> <span style="color: #007000"></bean></span> <span style="color: #007000"><bean</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"jdbcTemplate"</span> <span style="color: #0000C0">class=</span><span style="background-color: #fff0f0">"org.springframework.jdbc.core.JdbcTemplate"</span><span style="color: #007000">></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"dataSource"</span> <span style="color: #0000C0">ref=</span><span style="background-color: #fff0f0">"dataSource"</span> <span style="color: #007000">/></span> <span style="color: #007000"></bean></span> <span style="color: #808080"><!-- 3) JOB REPOSITORY - WE USE IN-MEMORY REPOSITORY FOR OUR EXAMPLE --></span> <span style="color: #007000"><bean</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"jobRepository"</span> <span style="color: #0000C0">class=</span><span style="background-color: #fff0f0">"org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"</span><span style="color: #007000">></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"transactionManager"</span> <span style="color: #0000C0">ref=</span><span style="background-color: #fff0f0">"transactionManager"</span> <span style="color: #007000">/></span> <span style="color: #007000"></bean></span> <span style="color: #808080"><!-- 4) LAUNCH JOBS FROM A REPOSITORY --></span> <span style="color: #007000"><bean</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"jobLauncher"</span> <span style="color: #0000C0">class=</span><span style="background-color: #fff0f0">"org.springframework.batch.core.launch.support.SimpleJobLauncher"</span><span style="color: #007000">></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"jobRepository"</span> <span style="color: #0000C0">ref=</span><span style="background-color: #fff0f0">"jobRepository"</span> <span style="color: #007000">/></span> <span style="color: #007000"></bean></span> <span style="color: #808080"><!-- 5) Beans representing the 2 job steps. --></span> <span style="color: #808080"><!-- Step1 - print hello world --></span> <span style="color: #007000"><bean</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"helloTask"</span> <span style="color: #0000C0">class=</span><span style="background-color: #fff0f0">"com.batch.simpletask.HelloTask"</span><span style="color: #007000">></span> <span style="color: #007000"><property</span> <span style="color: #0000C0">name=</span><span style="background-color: #fff0f0">"taskStartMessage"</span> <span style="color: #0000C0">value=</span><span style="background-color: #fff0f0">"Hello World - the time is now "</span> <span style="color: #007000">/></span> <span style="color: #007000"></bean></span> <span style="color: #808080"><!-- Step2 - print current time --></span> <span style="color: #007000"><bean</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"timeTask"</span> <span style="color: #0000C0">class=</span><span style="background-color: #fff0f0">"com.batch.simpletask.TimeTask"</span> <span style="color: #007000">/></span> <span style="color: #808080"><!-- 6) FINALLY OUR JOB DEFINITION. THIS IS A 2 STEP JOB --></span> <span style="color: #007000"><batch:job</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"simpleJob"</span><span style="color: #007000">></span> <span style="color: #007000"><batch:listeners></span> <span style="color: #007000"><batch:listener</span> <span style="color: #0000C0">ref=</span><span style="background-color: #fff0f0">"appJobExecutionListener"</span> <span style="color: #007000">/></span> <span style="color: #007000"></batch:listeners></span> <span style="color: #007000"><batch:step</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"step1"</span> <span style="color: #0000C0">next=</span><span style="background-color: #fff0f0">"step2"</span><span style="color: #007000">></span> <span style="color: #007000"><batch:tasklet</span> <span style="color: #0000C0">ref=</span><span style="background-color: #fff0f0">"helloTask"</span> <span style="color: #007000">/></span> <span style="color: #007000"></batch:step></span> <span style="color: #007000"><batch:step</span> <span style="color: #0000C0">id=</span><span style="background-color: #fff0f0">"step2"</span><span style="color: #007000">></span> <span style="color: #007000"><batch:tasklet</span> <span style="color: #0000C0">ref=</span><span style="background-color: #fff0f0">"timeTask"</span> <span style="color: #007000">/></span> <span style="color: #007000"></batch:step></span> <span style="color: #007000"></batch:job></span> <span style="color: #007000"></beans></span> |
- Use annotations to identify and autowire my spring beans.
- Ignore the data source configuration. It is not used for this example. Because I have a DAO I use for Part II & III this is here.
- Configure the job repository. Using an in-memory persistent store for this example.
- The job launcher.
- Register the two beans that make up my 2 steps for the job. One prints hello world and the other the time of the day.
- Last but not the least is the Job definition itself. Note the batch:listener which registers a listener to track job execution.
Now here is the code for the 2 steps in HelloTask.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span style="color: #008000; font-weight: bold">package</span> com<span style="color: #303030">.</span><span style="color: #0000C0">batch</span><span style="color: #303030">.</span><span style="color: #0000C0">simpletask</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.StepContribution</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.scope.context.ChunkContext</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.step.tasklet.Tasklet</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.repeat.RepeatStatus</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">class</span> <span style="color: #B00060; font-weight: bold">HelloTask</span> <span style="color: #008000; font-weight: bold">implements</span> Tasklet <span style="color: #303030">{</span> <span style="color: #008000; font-weight: bold">private</span> String taskStartMessage<span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">public</span> <span style="color: #303090; font-weight: bold">void</span> <span style="color: #0060B0; font-weight: bold">setTaskStartMessage</span><span style="color: #303030">(</span>String taskStartMessage<span style="color: #303030">)</span> <span style="color: #303030">{</span> <span style="color: #008000; font-weight: bold">this</span><span style="color: #303030">.</span><span style="color: #0000C0">taskStartMessage</span> <span style="color: #303030">=</span> taskStartMessage<span style="color: #303030">;</span> <span style="color: #303030">}</span> <span style="color: #008000; font-weight: bold">public</span> RepeatStatus <span style="color: #0060B0; font-weight: bold">execute</span><span style="color: #303030">(</span>StepContribution arg0<span style="color: #303030">,</span> ChunkContext arg1<span style="color: #303030">)</span> <span style="color: #008000; font-weight: bold">throws</span> Exception <span style="color: #303030">{</span> System<span style="color: #303030">.</span><span style="color: #0000C0">out</span><span style="color: #303030">.</span><span style="color: #0000C0">println</span><span style="color: #303030">(</span>taskStartMessage<span style="color: #303030">);</span> <span style="color: #008000; font-weight: bold">return</span> RepeatStatus<span style="color: #303030">.</span><span style="color: #0000C0">FINISHED</span><span style="color: #303030">;</span> <span style="color: #303030">}</span> <span style="color: #303030">}</span> |
And here is the 2nd tasklet – TimeTask.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span style="color: #008000; font-weight: bold">package</span> com<span style="color: #303030">.</span><span style="color: #0000C0">batch</span><span style="color: #303030">.</span><span style="color: #0000C0">simpletask</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">java.util.Calendar</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.StepContribution</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.scope.context.ChunkContext</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.step.tasklet.Tasklet</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.repeat.RepeatStatus</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">class</span> <span style="color: #B00060; font-weight: bold">TimeTask</span> <span style="color: #008000; font-weight: bold">implements</span> Tasklet <span style="color: #303030">{</span> <span style="color: #008000; font-weight: bold">public</span> RepeatStatus <span style="color: #0060B0; font-weight: bold">execute</span><span style="color: #303030">(</span>StepContribution arg0<span style="color: #303030">,</span> ChunkContext arg1<span style="color: #303030">)</span> <span style="color: #008000; font-weight: bold">throws</span> Exception <span style="color: #303030">{</span> System<span style="color: #303030">.</span><span style="color: #0000C0">out</span><span style="color: #303030">.</span><span style="color: #0000C0">println</span><span style="color: #303030">(</span>Calendar<span style="color: #303030">.</span><span style="color: #0000C0">getInstance</span><span style="color: #303030">().</span><span style="color: #0000C0">getTime</span><span style="color: #303030">());</span> <span style="color: #008000; font-weight: bold">return</span> RepeatStatus<span style="color: #303030">.</span><span style="color: #0000C0">FINISHED</span><span style="color: #303030">;</span> <span style="color: #303030">}</span> <span style="color: #303030">}</span> |
Last but not the least is my test driver that launches the batch itself. I use a Spring enabled JUnit test case to implement my driver.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<span style="color: #008000; font-weight: bold">package</span> com<span style="color: #303030">.</span><span style="color: #0000C0">batch</span><span style="color: #303030">.</span><span style="color: #0000C0">simpletask</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.apache.log4j.Logger</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.junit.Test</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.junit.runner.RunWith</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.Job</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.JobParameters</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.batch.core.launch.JobLauncher</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.beans.factory.annotation.Autowired</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.test.context.ContextConfiguration</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.test.context.junit4.SpringJUnit4ClassRunner</span><span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">import</span> <span style="color: #0e84b5; font-weight: bold">org.springframework.util.StopWatch</span><span style="color: #303030">;</span> <span style="color: #505050; font-weight: bold">@ContextConfiguration</span><span style="color: #303030">(</span>locations <span style="color: #303030">=</span> <span style="background-color: #fff0f0">"classpath:com/batch/simpletask/simpletaskletcontext.xml"</span><span style="color: #303030">)</span> <span style="color: #505050; font-weight: bold">@RunWith</span><span style="color: #303030">(</span>SpringJUnit4ClassRunner<span style="color: #303030">.</span><span style="color: #0000C0">class</span><span style="color: #303030">)</span> <span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">class</span> <span style="color: #B00060; font-weight: bold">SimpleTaskletTestCase</span> <span style="color: #303030">{</span> <span style="color: #008000; font-weight: bold">private</span> <span style="color: #008000; font-weight: bold">final</span> <span style="color: #008000; font-weight: bold">static</span> Logger logger <span style="color: #303030">=</span> Logger <span style="color: #303030">.</span><span style="color: #0000C0">getLogger</span><span style="color: #303030">(</span>SimpleTaskletTestCase<span style="color: #303030">.</span><span style="color: #0000C0">class</span><span style="color: #303030">);</span> <span style="color: #505050; font-weight: bold">@Autowired</span> <span style="color: #008000; font-weight: bold">private</span> JobLauncher jobLauncher<span style="color: #303030">;</span> <span style="color: #505050; font-weight: bold">@Autowired</span> <span style="color: #008000; font-weight: bold">private</span> Job job<span style="color: #303030">;</span> <span style="color: #008000; font-weight: bold">private</span> JobParameters jobParameters <span style="color: #303030">=</span> <span style="color: #008000; font-weight: bold">new</span> JobParameters<span style="color: #303030">();</span> <span style="color: #505050; font-weight: bold">@Test</span> <span style="color: #008000; font-weight: bold">public</span> <span style="color: #303090; font-weight: bold">void</span> <span style="color: #0060B0; font-weight: bold">testLaunchJob</span><span style="color: #303030">()</span> <span style="color: #008000; font-weight: bold">throws</span> Exception <span style="color: #303030">{</span> StopWatch sw <span style="color: #303030">=</span> <span style="color: #008000; font-weight: bold">new</span> StopWatch<span style="color: #303030">();</span> sw<span style="color: #303030">.</span><span style="color: #0000C0">start</span><span style="color: #303030">();</span> jobLauncher<span style="color: #303030">.</span><span style="color: #0000C0">run</span><span style="color: #303030">(</span>job<span style="color: #303030">,</span> jobParameters<span style="color: #303030">);</span> sw<span style="color: #303030">.</span><span style="color: #0000C0">stop</span><span style="color: #303030">();</span> logger<span style="color: #303030">.</span><span style="color: #0000C0">info</span><span style="color: #303030">(</span><span style="background-color: #fff0f0">">>> TIME ELAPSED:"</span> <span style="color: #303030">+</span> sw<span style="color: #303030">.</span><span style="color: #0000C0">prettyPrint</span><span style="color: #303030">());</span> <span style="color: #303030">}</span> <span style="color: #303030">}</span> |
Spring Batch – Part II – Flat File To Database – Read from a comma separated file and insert 200k rows into a HSQLDB database.
Spring Batch – Part III – From Database to Flat File – Read back the 200K rows and now write it out to a new file. Later.
You can download the Maven project from GitHub – https://github.com/thomasma/springbatch3part.