Spring Batch CompositeItemWriter如何管理委托编写者的事务?

时间:2018-08-18 01:23:31

标签: spring-batch compositeitemwriter

在批处理作业步骤配置中,我计划在writer中执行2个查询,第一个查询是更新表A中的记录,然后第二个查询是再次在表A中插入新记录。

到目前为止,我认为CompositeItemWriter可以实现我的上述目标,即,我需要创建2个JdbcBatchItemWriters,一个用于更新,另一个用于插入。

我的第一个问题是CompositeItemWriter是否适合上述要求?

如果是,这将导致有关交易的第二个问题。例如,如果第一次更新成功,而第二次插入失败。第一次更新交易会自动回滚吗?否则,如何在同一事务中手动提取两个更新?

谢谢!

1 个答案:

答案 0 :(得分:3)

  

我的第一个问题是CompositeItemWriter是否适合上述要求?

是的,CompositeItemWriter是必经之路。

  

如果是,这将导致有关交易的第二个问题。例如,如果第一次更新成功,而第二次插入失败。第一次更新交易会自动回滚吗?否则,如何在同一事务中手动提取两个更新?

很好的问题!是的,如果在第一个写入器中更新成功,然后在第二个写入器中插入失败,则所有语句将自动回滚。您需要知道的是,事务围绕着面向块的Tasklet步骤的执行(因此围绕着复合项目编写器的write方法)。因此,此方法(在委托编写器中执行)中所有sql语句的执行都是原子的。

为了说明这个用例,我编写了以下测试:

  • 给出一个表people,其中有两列idname,其中只有一个记录:1,'foo'
  • 让我们想象一下一个作业,该作业读取两个记录(1,'foo'2,'bar')并尝试将foo更新为foo!!,然后在表中插入2,'bar' 。这是通过具有两个项目编写者的CompositeItemWriter完成的:UpdateItemWriterInsertItemWriter
  • 用例是UpdateItemWriter成功但InsertItemWriter失败(抛出异常)
  • 预期结果是foo未更新为foo!!并且bar未插入表中(两个SQL语句由于{{1 }})

这是代码(它是独立的,因此您可以尝试一下,看看它是如何工作的,它使用了应该在类路径中的嵌入式hsqldb数据库):

InsertItemWriter

我的示例使用自定义项目编写器,但这也应与两个import java.util.Arrays; import java.util.List; import javax.sql.DataSource; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.item.support.CompositeItemWriter; import org.springframework.batch.item.support.ListItemReader; import org.springframework.batch.test.JobLauncherTestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.jdbc.JdbcTestUtils; @RunWith(SpringRunner.class) @ContextConfiguration(classes = TransactionWithCompositeWriterTest.JobConfiguration.class) public class TransactionWithCompositeWriterTest { @Autowired private JobLauncherTestUtils jobLauncherTestUtils; @Autowired private JdbcTemplate jdbcTemplate; @Before public void setUp() { jdbcTemplate.update("CREATE TABLE people (id INT IDENTITY NOT NULL PRIMARY KEY, name VARCHAR(20));"); jdbcTemplate.update("INSERT INTO people (id, name) VALUES (1, 'foo');"); } @Test public void testTransactionRollbackWithCompositeWriter() throws Exception { // given int peopleCount = JdbcTestUtils.countRowsInTable(jdbcTemplate, "people"); int fooCount = JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "people", "id = 1 and name = 'foo'"); int barCount = JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "people", "id = 2 and name = 'bar'"); Assert.assertEquals(1, peopleCount); Assert.assertEquals(1, fooCount); Assert.assertEquals(0, barCount); // when JobExecution jobExecution = jobLauncherTestUtils.launchJob(); // then Assert.assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); Assert.assertEquals("Something went wrong!", jobExecution.getAllFailureExceptions().get(0).getMessage()); StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); Assert.assertEquals(0, stepExecution.getCommitCount()); Assert.assertEquals(1, stepExecution.getRollbackCount()); Assert.assertEquals(0, stepExecution.getWriteCount()); peopleCount = JdbcTestUtils.countRowsInTable(jdbcTemplate, "people"); fooCount = JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "people", "id = 1 and name = 'foo'"); barCount = JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "people", "id = 2 and name = 'bar'"); Assert.assertEquals(1, peopleCount); // bar is not inserted Assert.assertEquals(0, barCount); // bar is not inserted Assert.assertEquals(1, fooCount); // foo is not updated to "foo!!" } @Configuration @EnableBatchProcessing public static class JobConfiguration { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") .addScript("/org/springframework/batch/core/schema-hsqldb.sql") .build(); } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean public ItemReader<Person> itemReader() { Person foo = new Person(1, "foo"); Person bar = new Person(2, "bar"); return new ListItemReader<>(Arrays.asList(foo, bar)); } @Bean public ItemWriter<Person> updateItemWriter() { return new UpdateItemWriter(dataSource()); } @Bean public ItemWriter<Person> insertItemWriter() { return new InsertItemWriter(dataSource()); } @Bean public ItemWriter<Person> itemWriter() { CompositeItemWriter<Person> compositeItemWriter = new CompositeItemWriter<>(); compositeItemWriter.setDelegates(Arrays.asList(updateItemWriter(), insertItemWriter())); return compositeItemWriter; } @Bean public Job job(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) { return jobBuilderFactory.get("job") .start(stepBuilderFactory .get("step").<Person, Person>chunk(2) .reader(itemReader()) .writer(itemWriter()) .build()) .build(); } @Bean public JobLauncherTestUtils jobLauncherTestUtils() { return new JobLauncherTestUtils(); } } public static class UpdateItemWriter implements ItemWriter<Person> { private JdbcTemplate jdbcTemplate; public UpdateItemWriter(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } @Override public void write(List<? extends Person> items) { for (Person person : items) { if ("foo".equalsIgnoreCase(person.getName())) { jdbcTemplate.update("UPDATE people SET name = 'foo!!' WHERE id = 1"); } } } } public static class InsertItemWriter implements ItemWriter<Person> { private JdbcTemplate jdbcTemplate; public InsertItemWriter(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } @Override public void write(List<? extends Person> items) { for (Person person : items) { if ("bar".equalsIgnoreCase(person.getName())) { jdbcTemplate.update("INSERT INTO people (id, name) VALUES (?, ?)", person.getId(), person.getName()); throw new IllegalStateException("Something went wrong!"); } } } } public static class Person { private long id; private String name; public Person() { } public Person(long id, String name) { this.id = id; this.name = name; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } } 一起使用。

我希望这会有所帮助!