问题
I have Spring Batch job that writes to the database (it has a step with a JpaItemWriter
). I have an integration test such as follows:
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("integrationTest")
public class LoadApplicationTests {
@Autowired
private Job job;
@Autowired
private JobRepository jobRepository;
@Autowired
private JobLauncher jobLauncher;
private JobLauncherTestUtils jobLauncherTestUtils;
@Before
public void setUp() throws IOException, java.text.ParseException, Exception {
jobLauncherTestUtils = new JobLauncherTestUtils();
jobLauncherTestUtils.setJob(job);
jobRepository = new MapJobRepositoryFactoryBean(new ResourcelessTransactionManager()).getObject();
jobLauncherTestUtils.setJobRepository(jobRepository);
jobLauncherTestUtils.setJobLauncher(jobLauncher);
}
@Test
public void testJob() throws Exception {
JobParametersBuilder j = new JobParametersBuilder();
JobParameters jobParameters = j.addDate("runDate", new Date())
.addString("file", testFile.getAbsolutePath())
.addString("override", "false")
.addString("weekly", "false")
.toJobParameters();
JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobParameters);
Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
}
}
When running the job in the test, it commits to the database. How can I prevent committing to the database? Normally, I could add @Transactional
to rollback the transaction after each test. However, when I add the annotation the test class, I receive:
java.lang.IllegalStateException: Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove @Transactional annotations from client).
Update
I have tried to add @Rollback
to the test class. However, the JpaItemWriter
still commits.
Here's the configuration for the transaction manager in the application code:
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
@Bean
public Step stepLoadFile(StepBuilderFactory stepBuilderFactory,
PlatformTransactionManager transactionManager,
ItemReader<MyClass> reader, ItemProcessor<MyClass,
MyClass> processor,
ItemWriter<MyClass> writer,
ReadFailureHandler readListenerSupport,
WriteFailureHandler writeListenerSupport) {
Step step = stepBuilderFactory.get("stepPersistFile")
.transactionManager(transactionManager)
.<MyClass, MyClass> chunk(1000)
.reader(reader)
.processor(processor)
.listener(writeListenerSupport)
.listener(readListenerSupport)
.writer(writer)
.build();
return step;
}
回答1:
To overcome this my group has simply written an @After
hook to clear the data that was written. It is not pretty, nor desired, but it appears to be getting us through our issues.
Keep in mind, that this will still write your job executions to batch_job_execution
, batch_job_execution_context
, etc.
Also recognize, that you'll probably want to ensure your spring.batch.job.enabled
should be set to false
in your test/resources/application.[properties|yml]
.
Fun times 🤦♂️
回答2:
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
may be what you are looking for, and is what I have used in the past when testing programs which alter database records. Based on the Spring Docs, @DirtiesContext
is a
Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache. Use this annotation if a test has modified the context — for example, by modifying the state of a singleton bean, modifying the state of an embedded database, etc. Subsequent tests that request the same context will be supplied a new context.
Otherwise, building off of @Dan's answer, TestTransaction
may allow for more explicit control of your test transactions within methods annotated with @Test
, @Before
, and @After
. See here for more info.
回答3:
I suppose you have transactionManager, if so, add
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
at the top of your test class.
回答4:
Normally the strategy I use for integration tests is have a profile
(as you have) with Embedded DB
creation for the integration tests, with a specific application-test.properties
so then the impact of them are more controlled when you are concerned about data modification and these kind of stuffs.
Regarding your case, it looks that you are in the right path adding the @Transactional
for tests purposes, as we can see on this topic. Then I would invest my time investigating further about the exception you get when this error happens.
java.lang.IllegalStateException: Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove @Transactional annotations from client).
Also this exception looks something quite known, so I would recommend to take a look on this other topic to figure out what was tried. They had pretty much the same issue and wrote an article about how to avoid this error happening (also on this thread).
Another possibility commented is upgrade spring code version, because in some cases this issues started happening right after the upgrade.
Now, talking more about the components used, you look using spring-batch
, which has its particular suite to test the batch slices and etc, so I would recommend you to take a look on how to use/test spring-batch and the spring-batch test documentation itself. Maybe using the components provided by spring-boot
for batch testing there are pre-made decisions and strategies that mitigate your issue.
来源:https://stackoverflow.com/questions/39620421/spring-batch-how-to-prevent-database-commits-with-joblaunchertestutils