Announcement Announcement Module
Collapse
No announcement yet.
run batch job with @Scheduled annotation and incrementer Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • run batch job with @Scheduled annotation and incrementer

    Hey folks,

    I have a job which ran nicely with Quartz, but I want to get it working with @Scheduled.

    Here's my job definition:

    Code:
    	<batch:job id="myJob" job-repository="jobRepository" incrementer="jobParametersIncrementer">
    		
            <batch:step id="step1" parent="taskletStep" next="step2">
                <batch:tasklet ref="fooTask">
    	            <batch:listeners>
    					<batch:listener ref="promotionListener"/>
    	            </batch:listeners>
                </batch:tasklet>
            </batch:step>
            <batch:step id="step2" parent="taskletStep" next="step3">
                <batch:tasklet ref="barTask">
    	            <batch:listeners>
    					<batch:listener ref="promotionListener"/>
    		        </batch:listeners>
                </batch:tasklet>
            </batch:step>
            <batch:step id="step3" parent="taskletStep">
            	<batch:tasklet ref="bazTask">
            	</batch:tasklet>
            </batch:step>
        </batch:job>
    
        <context:component-scan base-package="org/example/batch"/>
    	<task:annotation-driven/>
    
    	<bean id="jobLauncher"
    		class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    		<property name="jobRepository" ref="jobRepository" />
    	</bean>
    	
    	<bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
    		<property name="jobRegistry" ref="jobRegistry"/>
    	</bean>
    	
    	<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
    
    	<bean id="taskletStep" abstract="true"
    		class="org.springframework.batch.core.step.tasklet.TaskletStep">
    		<property name="jobRepository" ref="jobRepository" />
    		<property name="transactionManager" ref="transactionManager" />
    	</bean>
    	
    	<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
    		<description>
    			PS jdbc:embedded-database tag spawns an HSQL db by default.
    			Supported databaseType otherwise can be found here:
    			http://static.springsource.org/spring-batch/apidocs/org/springframework/batch/support/DatabaseType.html
    		</description>
            <property name="dataSource" ref="dataSourceBatch"/>
            <property name="databaseType" value="HSQL"/>
            <property name="transactionManager" ref="transactionManagerBatch"/>
        </bean>
        
        <jdbc:embedded-database id="dataSourceBatch" type="HSQL">
        	<jdbc:script location="classpath:batch-jobrepository.sql"></jdbc:script>
        </jdbc:embedded-database>
    
    	<bean id="jobParametersIncrementer" class="org.springframework.batch.core.launch.support.RunIdIncrementer"/>
    
    	<bean id="transactionManagerBatch" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    		<property name="dataSource" ref="dataSourceBatch"></property>
    	</bean>
    The code that is supposed to run this job looks like this, but it's not re-runable:

    Code:
    @Service
    public class JobRunner {
    	
    	@Autowired
    	private JobLocator jobLocator;
    
    	@Autowired
    	private JobLauncher jobLauncher;
    
    	//@Scheduled(fixedDelay = Long.valueOf("${webapi.myJob.fixedDelay}").longValue()) //DOES NOT COMPILE
    	//@Scheduled(fixedDelay = ${webapi.myJob.fixedDelay}) //DOES NOT COMPILE
    	@Scheduled(fixedDelay = 3000L)
    	public void runMyJob() throws Exception {
    		jobLauncher.run(jobLocator.getJob("myJob"), new JobParameters());
            }
    }
    The job runs once, but then:

    Code:
    JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={}.  If you want to run this job again, change the parameters.
    A few questions:
    1. Why does the incrementer not have any effect?
    2. Is there a better way to run a job? I suspect "new JobParameters()" to break the incrementer.
    3. How do I get my fixedDelay in the annotation by using the ${} notation?

    Thanks in advance!
    Last edited by opyate; Mar 5th, 2010, 06:40 AM.

  • #2
    PS, I tried doing the following in my JobRunner:

    Code:
    @Service
    public class JobRunner {
    	
    	@Autowired
    	private JobLocator jobLocator;
    
    	@Autowired
    	private JobLauncher jobLauncher;
    	
    	@Autowired
    	private JobParametersIncrementer jobParametersIncrementer;
    
    	@Autowired
    	private Job myJob;
    
    	@Scheduled(fixedDelay = 3000L)
    	public void runMyJob() throws Exception {
    		//jobLauncher.run(jobLocator.getJob("myJob"), jobParametersIncrementer.getNext(new JobParameters()));
    		jobLauncher.run(jobLocator.getJob("myJob"), myJob.getJobParametersIncrementer().getNext(new JobParameters()));
    	}
    }
    It runs once, then spits this out every 3 seconds after:

    Code:
    JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={run.id=1}.  If you want to run this job again, change the parameters.
    I tried a few other things, like injecting the jobParameters as a singleton, or instantiating it in the JobRunner class scope (not in the runMyJob() method scope).
    Last edited by opyate; Mar 5th, 2010, 06:46 AM.

    Comment


    • #3
      Hey guys, I fixed it, but I don't think it's a very elegant solution :-(

      Extra config:

      Code:
      	<util:properties id="myJobProperties">
      		<prop key="run.id">0</prop>
      	</util:properties>
      Modified version of RunIdIncrementer (to get rid of "Can't cast String to Long" messages):

      Code:
      public class TypeSafeRunIdIncrementer implements JobParametersIncrementer {
      
      	private static String RUN_ID_KEY = "run.id";
      
      	/**
      	 * Increment the run.id parameter.
      	 */
      	public JobParameters getNext(JobParameters parameters) {
      		if (parameters == null || parameters.isEmpty()) {
      			return new JobParametersBuilder().addLong(RUN_ID_KEY, 1L).toJobParameters();
      		}
      		long id = Long.valueOf(parameters.getString(RUN_ID_KEY)).longValue() + 1L;
      		return new JobParametersBuilder().addLong(RUN_ID_KEY, id).toJobParameters();
      	}
      
      }

      And a modified JobRunner:

      Code:
      @Service
      public class JobRunner {
      	
      	@Autowired
      	private JobLocator jobLocator;
      
      	@Autowired
      	private JobLauncher jobLauncher;
      	
      	@Autowired
      	private JobParametersIncrementer jobParametersIncrementer;
      
      	@Autowired
      	private Job myJob;
      
      	@Autowired
      	private Properties myJobProperties;
      	
      	private JobParameters myJobParameters = (new DefaultJobParametersConverter()).getJobParameters(myJobProperties);
      
      	@Scheduled(fixedDelay = 3000L)
      	public void runMyJob() throws Exception {
      		myJobParameters = myJob.getJobParametersIncrementer().getNext(myJobParameters);
      		jobLauncher.run(jobLocator.getJob("myJob"), myJobParameters);
      	}
      }

      I really just want to be able to inject the job, and call a ".run()" on it, or something similarly simple.

      Thanks.

      Comment


      • #4
        You need to pull out the last good parameters to pass into the incrementer. Take a look at the way JobOperator.startNextInstance() is implemented. There's also a different version of the same logic in Spring Batch Admin in the JobService which tries to be smart about guessing what to do next (either restart or startNext depending on the context).

        Comment

        Working...
        X