Announcement Announcement Module
Collapse
No announcement yet.
Processed items not bound to transaction Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Processed items not bound to transaction

    Hi
    I have a ItemOrientedStep interacting with a RepeatTemplate. The batch has one record. The one record is correctly processed in the first transaction. Then in the second transaction there are no records and I get a 'Processed items not bound to transaction' exception. What do I change not to get this error?
    Code:
    java.lang.IllegalStateException: Processed items not bound to transaction.
    	at org.springframework.util.Assert.state(Assert.java:384)
    	at org.springframework.batch.item.database.BatchSqlUpdateItemWriter.getProcessed(BatchSqlUpdateItemWriter.java:122)
    	at org.springframework.batch.item.database.BatchSqlUpdateItemWriter.doFlush(BatchSqlUpdateItemWriter.java:178)
    	at org.springframework.batch.item.database.BatchSqlUpdateItemWriter.flush(BatchSqlUpdateItemWriter.java:220)
    	at com.blah.billingmigration.BatchSqlBillingInformationWriter.flush(BatchSqlBillingInformationWriter.java:66)

  • #2
    You have one record but two transactions?

    Comment


    • #3
      Sorry, I read again and I think I understand a little bit better, but can you clarify what you mean by 'there are no records in the second transaction' ?

      Comment


      • #4
        1 record

        there is only one record. that's it. Yet, there are 2 transactions and the 2nd one throws the error above.

        Comment


        • #5
          There can't be a second transaction, because the error above will only happen if there's not a transaction. I'm still confused by the 'one record' comment as well.

          Can you post your configuration? That might help ease the confusion.

          Comment


          • #6
            sample

            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.

            Comment


            • #7
              There's a transaction per record because you haven't set the commitInterval property on the step. It defaults to one. Although, there's a little bug in rc1 that does cause some issues if no commitInterval is set (instead of defaulting to one) However, I *think* the abstract SimpleStep had a commit interval in rc1. Trunk definitely does, but I don't have a copy of RC1 on my machine.

              The problem isn't that there's a 'second transaction', although I think I understand what you mean better, since there should be a transaction per 'chunk'. However, your writer shouldn't be called a second time after outputting the first record. When the contents of the table you're reading from are empty, it should return null and exit the job, never calling the writer again. I'm honestly a little bit stumped as to how it could call your writer twice if you have one record. It could be the late hour though, I'll take a look tomorrow and see if I can figure it out.

              Comment


              • #8
                Actually, just had an idea after I posted the last message. I bet the problem isn't that the writer is being called twice, but that the flush might be called twice. I can't seem to find any place where that is happening, but it would explain your bug. As a workaround you can just use a normal dao as a writer. Honestly, unless you're loading massive amounts of data with a large commit interval, the extra performance advantage of jdbc batch mode won't gain you much over just doing the update on each write. This is especially true if your data is even slightly dirty, since any error hit while flushing (writing out the batched data) will require the second try to lower the commit interval to one, so that the bad item can be found.

                I'll see if I can recreate tomorrow, but since we're releasing on Friday, if I do find a bug, it might not make it in the official release, but will end up in the first bug fix release instead.

                Comment


                • #9
                  Increased interval to 10

                  Hi
                  I will read your posts again more carefully for ideas. But you were right, if I change the commitInterval to anything other than 1, I do not get the error:
                  Code:
                  <bean id="simpleStep" class="org.springframework.batch.core.step.item.SimpleStepFactoryBean"
                  		abstract="true">
                  		<property name="transactionManager" ref="transactionManager" />
                  		<property name="jobRepository" ref="jobRepository" />
                  		<property name="startLimit" value="100" />
                  		<property name="commitInterval" value="10" />
                  	</bean>
                  The writer is not called twice. There is a second, extra transaction if the commitInterval is 1 and there is 1 record. That does sound like a bug.
                  Thanks for your help.
                  I will post an other thread to find out how to create job that exits and does not stay up running.

                  Comment


                  • #10
                    The one extra transaction is actually expected when "record_count mod commit_interval == 0" i.e. always when commit interval is one. This is because after processing all items, you still need to call the reader once more and it returns null, indicating there are no more items to process.

                    Comment


                    • #11
                      http://jira.springframework.org/browse/BATCH-517

                      Comment

                      Working...
                      X