
Originally Posted by
tbone21w
Okay, I got the job to run and figured out that I had a cyclic reference in the config (xml) files imports. I added a new configuration for the sessionFactory in the launcher xml and referenced the service object and DAOs by putting their config files on the classpath and imported them in my launcher xml. Then I kicked off the job with the command line runner...
CommandLineJobRunner.main(new String[] {"InventoryJob.xml", "inventoryDailyJob", "schedule.date(string)=" + df.format(new Date())});
The above turned out to have a few issue, the appserver was shut down after each run and when running on tomcat I kept getting a random null pointer exception that looked like it was loosing connections?? I ended up kicking off the job with the job launcher I had configured in my launch xml like so..
JobParameters jobParameters = new JobParametersBuilder().addDate ("schedule.date", new Date());
JobExecution run = jobLauncher.run(job, jobParameters);
I also added the SimpleAsyncTaskExecutor to the job launcher and now the servlet is not waiting on the job. I think I got this resolved.
You don't want to use a commandline runner to launch a batch job from within your web application.
I'm using the following:
Code:
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
<property name="taskExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
</property>
</bean>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="databaseType" value="sqlserver"/>
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>
Then I launch the job from within a helper class called by my controller:
Code:
Job batchJob = jobFactory.createJob();
// You would want to add any parameters for your job here
JobParameters parameters = new JobParameters(map, new HashMap<String,Long>(), new HashMap<String,Double>(), new HashMap<String,Date>());
// Now, kick off the job
jobLauncher.run(batchJob, parameters);
Note that many of the beans that you will interact with in a batch job are stateful so you cannot define them as spring singleton beans. What I did was wire up all of my job beans and their associated readers/writers in a separate spring bean config that is not loaded into my app context at startup. Then, I use this implementation for my job factory so that everytime that I create a job to run, it is created in it's own sub context using the parent app context of the web app and the prototype beans in the separate bean config:
Code:
public class ContextAwareJobFactory implements JobFactory, ApplicationContextAware, InitializingBean
{
private ClassPathXmlApplicationContextJobFactory delegate;
/* The parent application context */
private ApplicationContext applicationContext;
/* The job bean name */
private String beanName;
/* resource path to subcontext spring config */
private String subcontextPath;
public void setBeanName(String beanName)
{
this.beanName = beanName;
}
public void setSubcontextPath(String subcontextPath)
{
this.subcontextPath = subcontextPath;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.configuration.JobFactory#createJob()
*/
@Override
public Job createJob()
{
return delegate.createJob();
}
/* (non-Javadoc)
* @see org.springframework.batch.core.configuration.JobFactory#getJobName()
*/
@Override
public String getJobName()
{
return delegate.getJobName();
}
/* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException
{
this.applicationContext = context;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception
{
delegate = new ClassPathXmlApplicationContextJobFactory(this.beanName, this.subcontextPath, this.applicationContext);
}
}
The job factory is declared as such:
Code:
<bean id="jobFactory" class="com.foobar.ContextAwareJobFactory">
<property name="beanName" value="provisioningBatchUploadJob"/>
<property name="subcontextPath" value="classpath:spring/fubar-project-batch-processing-prototype-beans.xml"/>
</bean>
The code in my helper class above is invoked directly from within my spring mvc controller. In my case, I'm taking an uploaded file from the user and using that as the source of the batch input. After futzing around with it for a few days, I finally got all the issues sorted out and now it is working like a champ. I've even verified that the restart behavior works correctly, allowing an admin to restart a failed batch by fixing the input file and restarting from the web interface.