Hello
I converted to football sample. I need to read each record from a table, retrieve some data from web services and update some fields. The configuration:
Code:
<bean id="billingJob" parent="simpleJob">
<property name="steps">
<list>
<bean id="playerSummarization" parent="simpleStep">
<property name="itemReader"
ref="playerSummarizationSource" />
<property name="itemWriter" ref="itemWriter" />
</bean>
</list>
</property>
</bean>
<bean id="playerSummarizationSource"
class="org.springframework.batch.item.database.JdbcCursorItemReader">
<property name="dataSource" ref="dataSource" />
<property name="mapper">
<bean
class="com.blah.billingmigration.BillingInformationMapper" />
</property>
<property name="sql">
<value>
SELECT ID, YEAR FROM VLT_BILLINGTEST
</value>
</property>
</bean>
<bean id="itemWriter" class="com.blah.billingmigration.BatchSqlBillingInformationWriter">
<property name="delegate">
<bean class="org.springframework.batch.item.database.BatchSqlUpdateItemWriter">
<property name="jdbcTemplate" ref="jdbcTemplate" />
<property name="sql"><value><![CDATA[UPDATE VLT_BILLINGTEST SET YEAR=?,GREETING=? WHERE ID=?]]></value></property>
<property name="itemPreparedStatementSetter">
<bean class="com.blah.billingmigration.BillingInformationUpdatePreparedStatementSetter"/>
</property>
</bean>
</property>
</bean>
The code:
Code:
public class BillingInformationMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
BillingInformation billingInformation = new BillingInformation();
billingInformation.setId(new Long(rs.getInt(1)));
billingInformation.setYear(rs.getString(2));
return billingInformation;
}
}
public class BatchSqlBillingInformationWriter implements ItemWriter, InitializingBean {
private ItemWriter delegate;
public void setDelegate(ItemWriter delegate) {
this.delegate = delegate;
}
public void afterPropertiesSet() throws Exception {
Assert.state(delegate instanceof BatchSqlUpdateItemWriter, "Delegate must be set and must be an instance of BatchSqlUpdateItemWriter");
}
public void write(Object data) throws Exception {
BillingInformation billingInformation = (BillingInformation) data;
// add the stuff from web services, etc. here
billingInformation.setGreeting("Hello World!");
delegate.write(billingInformation);
}
public void clear() throws ClearFailedException {
delegate.clear();
}
public void flush() throws FlushFailedException {
delegate.flush();
}
}
public class BillingInformationUpdatePreparedStatementSetter implements ItemPreparedStatementSetter {
public void setValues(Object item, PreparedStatement ps) throws SQLException {
BillingInformation billingInformation = (BillingInformation) item;
ps.setString(1, billingInformation.getYear());
ps.setString(2, billingInformation.getGreeting());
ps.setInt(3, billingInformation.getId().intValue());
}
}
BatchSqlBillingInformationWriter does not yet call the web service, just sets a value for a test. The rest of the configuration is same as for the football sample except there is no Hibernate so that transaction manager is:
Code:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
I could not figure out an other way to launch the job so it looks like:
Code:
public class TaskExecutorLauncher implements ResourceLoaderAware {
private JobRegistry registry;
private ResourceLoader resourceLoader;
private ApplicationContext parentContext = null;
public void setRegistry(JobRegistry registry) {
this.registry = registry;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
private void register(String[] paths) throws DuplicateJobException {
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(resourceLoader.getResource(path),
parentContext.getAutowireCapableBeanFactory());
String[] names = beanFactory.getBeanNamesForType(Job.class);
for (int j = 0; j < names.length; j++) {
registry.register(new ClassPathXmlApplicationContextJobFactory(names[j], path, parentContext));
}
}
}
public static void main(String[] args) throws Exception {
final TaskExecutorLauncher launcher = new TaskExecutorLauncher();
new Thread(new Runnable() {
public void run() {
launcher.run();
};
}).start();
while (launcher.parentContext == null) {
Thread.sleep(100L);
}
// Paths to individual job configurations.
final String[] paths = new String[] { "billingmigration/adhocLoopJob.xml", "billingmigration/billingMigrationJob.xml" };
launcher.register(paths);
SimpleJobLauncher simpleJobLauncher = (SimpleJobLauncher)launcher.parentContext.getBean("jobLauncher");
final JobParameters jobParameters = new JobParameters();
simpleJobLauncher.run(launcher.registry.getJob("billingJob"), jobParameters);
//
System.out
.println("Started application. "
+ "Please connect using JMX (remember to use -Dcom.sun.management.jmxremote if you can't see anything in Jconsole).");
System.in.read();
}
private void run() {
final ApplicationContext parent = new ClassPathXmlApplicationContext("billingmigration/adhoc-job-launcher-context.xml");
parent.getAutowireCapableBeanFactory().autowireBeanProperties(this,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
parent.getAutowireCapableBeanFactory().initializeBean(this, "taskExecutorLauncher");
this.parentContext = parent;
}
}
So when the program starts there is only one record in the table. And there is still one record after the 1st transaction commits. The 2nd transaction throws the error.
It does not matter how many records are there. There is always an extra transaction with an error. Also, it does not look right why there is a transaction for each record.
Thanks.