Skip to content

Commit ca43230

Browse files
committed
Polish "Add option to allow Spring Batch custom isolation levels"
See gh-28859
1 parent a8d1d31 commit ca43230

File tree

6 files changed

+107
-121
lines changed

6 files changed

+107
-121
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BasicBatchConfigurer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.springframework.batch.core.repository.JobRepository;
2727
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
2828
import org.springframework.beans.factory.InitializingBean;
29+
import org.springframework.boot.autoconfigure.batch.BatchProperties.Isolation;
2930
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
3031
import org.springframework.boot.context.properties.PropertyMapper;
3132
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@@ -139,7 +140,8 @@ protected JobRepository createJobRepository() throws Exception {
139140
* @return the isolation level or {@code null} to use the default
140141
*/
141142
protected String determineIsolationLevel() {
142-
return this.properties.getJdbc().getIsolationLevelForCreate();
143+
Isolation isolation = this.properties.getJdbc().getIsolationLevelForCreate();
144+
return (isolation != null) ? isolation.toIsolationName() : null;
143145
}
144146

145147
protected PlatformTransactionManager createTransactionManager() {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchProperties.java

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ public static class Jdbc {
6666
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/"
6767
+ "batch/core/schema-@@platform@@.sql";
6868

69+
/**
70+
* Transaction isolation level to use when creating job meta-data for new jobs.
71+
* Auto-detected based on whether JPA is being used or not.
72+
*/
73+
private Isolation isolationLevelForCreate;
74+
6975
/**
7076
* Path to the SQL file to use to initialize the database schema.
7177
*/
@@ -87,10 +93,13 @@ public static class Jdbc {
8793
*/
8894
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
8995

90-
/**
91-
* Transaction isolation level to use when creating job meta-data for new jobs.
92-
*/
93-
private String isolationLevelForCreate;
96+
public Isolation getIsolationLevelForCreate() {
97+
return this.isolationLevelForCreate;
98+
}
99+
100+
public void setIsolationLevelForCreate(Isolation isolationLevelForCreate) {
101+
this.isolationLevelForCreate = isolationLevelForCreate;
102+
}
94103

95104
public String getSchema() {
96105
return this.schema;
@@ -124,12 +133,45 @@ public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
124133
this.initializeSchema = initializeSchema;
125134
}
126135

127-
public String getIsolationLevelForCreate() {
128-
return this.isolationLevelForCreate;
129-
}
136+
}
130137

131-
public void setIsolationLevelForCreate(String isolationLevelForCreate) {
132-
this.isolationLevelForCreate = isolationLevelForCreate;
138+
/**
139+
* Available transaction isolation levels.
140+
*/
141+
public enum Isolation {
142+
143+
/**
144+
* Use the default isolation level of the underlying datastore.
145+
*/
146+
DEFAULT,
147+
148+
/**
149+
* Indicates that dirty reads, non-repeatable reads and phantom reads can occur.
150+
*/
151+
READ_UNCOMMITTED,
152+
153+
/**
154+
* Indicates that dirty reads are prevented; non-repeatable reads and phantom
155+
* reads can occur.
156+
*/
157+
READ_COMMITTED,
158+
159+
/**
160+
* Indicates that dirty reads and non-repeatable reads are prevented; phantom
161+
* reads can occur.
162+
*/
163+
REPEATABLE_READ,
164+
165+
/**
166+
* Indicate that dirty reads, non-repeatable reads and phantom reads are
167+
* prevented.
168+
*/
169+
SERIALIZABLE;
170+
171+
private static final String PREFIX = "ISOLATION_";
172+
173+
String toIsolationName() {
174+
return PREFIX + name();
133175
}
134176

135177
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JpaBatchConfigurer.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
2424

25+
import org.springframework.boot.autoconfigure.batch.BatchProperties.Isolation;
2526
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
2627
import org.springframework.orm.jpa.JpaTransactionManager;
2728
import org.springframework.transaction.PlatformTransactionManager;
@@ -38,8 +39,6 @@ public class JpaBatchConfigurer extends BasicBatchConfigurer {
3839

3940
private final EntityManagerFactory entityManagerFactory;
4041

41-
private final String isolationLevelForCreate;
42-
4342
/**
4443
* Create a new {@link BasicBatchConfigurer} instance.
4544
* @param properties the batch properties
@@ -52,18 +51,19 @@ protected JpaBatchConfigurer(BatchProperties properties, DataSource dataSource,
5251
TransactionManagerCustomizers transactionManagerCustomizers, EntityManagerFactory entityManagerFactory) {
5352
super(properties, dataSource, transactionManagerCustomizers);
5453
this.entityManagerFactory = entityManagerFactory;
55-
this.isolationLevelForCreate = properties.getJdbc().getIsolationLevelForCreate();
5654
}
5755

5856
@Override
5957
protected String determineIsolationLevel() {
60-
if (this.isolationLevelForCreate == null) {
61-
logger.warn(
62-
"JPA does not support custom isolation levels, so locks may not be taken when launching Jobs. Define spring.batch.jdbc.isolation-level-for-create property to force a custom isolation level.");
63-
return "ISOLATION_DEFAULT";
58+
String name = super.determineIsolationLevel();
59+
if (name != null) {
60+
return name;
61+
}
62+
else {
63+
logger.warn("JPA does not support custom isolation levels, so locks may not be taken when launching Jobs. "
64+
+ "To silence this warning, set 'spring.batch.jdbc.isolation-level-for-create' to 'default'.");
65+
return Isolation.DEFAULT.toIsolationName();
6466
}
65-
66-
return this.isolationLevelForCreate;
6767
}
6868

6969
@Override

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.sql.DataSource;
2424

2525
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.api.extension.ExtendWith;
2627

2728
import org.springframework.batch.core.BatchStatus;
2829
import org.springframework.batch.core.Job;
@@ -57,6 +58,8 @@
5758
import org.springframework.boot.sql.init.DatabaseInitializationMode;
5859
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
5960
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
61+
import org.springframework.boot.test.system.CapturedOutput;
62+
import org.springframework.boot.test.system.OutputCaptureExtension;
6063
import org.springframework.context.annotation.Bean;
6164
import org.springframework.context.annotation.Configuration;
6265
import org.springframework.context.annotation.Primary;
@@ -78,6 +81,7 @@
7881
* @author Vedran Pavic
7982
* @author Kazuki Shimizu
8083
*/
84+
@ExtendWith(OutputCaptureExtension.class)
8185
class BatchAutoConfigurationTests {
8286

8387
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@@ -208,8 +212,30 @@ void testUsingJpa() {
208212
// level)
209213
assertThat(context.getBean(JobRepository.class).getLastJobExecution("job", new JobParameters()))
210214
.isNull();
211-
assertThat(context.getBean(JobRepository.class))
212-
.satisfies(JobRepositoryTestingSupport.isolationLevelRequirements("ISOLATION_DEFAULT"));
215+
});
216+
}
217+
218+
@Test
219+
void testDefaultIsolationLevelWithJpaLogsWarning(CapturedOutput output) {
220+
this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
221+
HibernateJpaAutoConfiguration.class).run((context) -> {
222+
assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel())
223+
.isEqualTo("ISOLATION_DEFAULT");
224+
assertThat(output).contains("JPA does not support custom isolation levels")
225+
.contains("set 'spring.batch.jdbc.isolation-level-for-create' to 'default'");
226+
});
227+
}
228+
229+
@Test
230+
void testCustomIsolationLevelWithJpaDoesNotLogWarning(CapturedOutput output) {
231+
this.contextRunner.withPropertyValues("spring.batch.jdbc.isolation-level-for-create=default")
232+
.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
233+
HibernateJpaAutoConfiguration.class)
234+
.run((context) -> {
235+
assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel())
236+
.isEqualTo("ISOLATION_DEFAULT");
237+
assertThat(output).doesNotContain("JPA does not support custom isolation levels")
238+
.doesNotContain("set 'spring.batch.jdbc.isolation-level-for-create' to 'default'");
213239
});
214240
}
215241

@@ -234,16 +260,6 @@ void testRenamePrefix() {
234260
});
235261
}
236262

237-
@Test
238-
void testCustomIsolationLevelForCreate() {
239-
this.contextRunner
240-
.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
241-
HibernateJpaAutoConfiguration.class)
242-
.withPropertyValues("spring.batch.jdbc.isolation-level-for-create:ISOLATION_READ_COMMITTED")
243-
.run((context) -> assertThat(context.getBean(JobRepository.class))
244-
.satisfies(JobRepositoryTestingSupport.isolationLevelRequirements("ISOLATION_READ_COMMITTED")));
245-
}
246-
247263
@Test
248264
void testCustomizeJpaTransactionManagerUsingProperties() {
249265
this.contextRunner

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationWithoutJpaTests.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@
2727
import org.springframework.batch.core.repository.JobRepository;
2828
import org.springframework.boot.autoconfigure.AutoConfigurations;
2929
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
30-
import org.springframework.boot.autoconfigure.batch.BatchProperties.Jdbc;
3130
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
3231
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
3332
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
@@ -38,7 +37,6 @@
3837
import org.springframework.transaction.PlatformTransactionManager;
3938

4039
import static org.assertj.core.api.Assertions.assertThat;
41-
import static org.assertj.core.api.Assertions.from;
4240

4341
/**
4442
* Tests for {@link BatchAutoConfiguration} when JPA is not on the classpath.
@@ -61,24 +59,19 @@ void jdbcWithDefaultSettings() {
6159
assertThat(context).hasSingleBean(PlatformTransactionManager.class);
6260
assertThat(context.getBean(PlatformTransactionManager.class).toString())
6361
.contains("DataSourceTransactionManager");
64-
assertThat(context.getBean(BatchProperties.class).getJdbc())
65-
.returns("classpath:org/springframework/batch/core/schema-@@platform@@.sql",
66-
from(Jdbc::getSchema))
67-
.returns(DatabaseInitializationMode.EMBEDDED, from(Jdbc::getInitializeSchema))
68-
.returns(null, from(Jdbc::getIsolationLevelForCreate));
62+
assertThat(context.getBean(BatchProperties.class).getJdbc().getInitializeSchema())
63+
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
64+
assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel()).isNull();
6965
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
7066
.queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty();
7167
assertThat(context.getBean(JobExplorer.class).findRunningJobExecutions("test")).isEmpty();
7268
assertThat(context.getBean(JobRepository.class).getLastJobExecution("test", new JobParameters()))
7369
.isNull();
74-
75-
assertThat(context.getBean(JobRepository.class)).satisfies(
76-
JobRepositoryTestingSupport.isolationLevelRequirements("ISOLATION_SERIALIZABLE"));
7770
});
7871
}
7972

8073
@Test
81-
void jdbcWithCustomSettings() {
74+
void jdbcWithCustomPrefix() {
8275
this.contextRunner.withUserConfiguration(DefaultConfiguration.class, EmbeddedDataSourceConfiguration.class)
8376
.withPropertyValues("spring.datasource.generate-unique-name=true",
8477
"spring.batch.jdbc.schema:classpath:batch/custom-schema-hsql.sql",
@@ -92,6 +85,15 @@ void jdbcWithCustomSettings() {
9285
});
9386
}
9487

88+
@Test
89+
void jdbcWithCustomIsolationLevel() {
90+
this.contextRunner.withUserConfiguration(DefaultConfiguration.class, EmbeddedDataSourceConfiguration.class)
91+
.withPropertyValues("spring.datasource.generate-unique-name=true",
92+
"spring.batch.jdbc.isolation-level-for-create=read_committed")
93+
.run((context) -> assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel())
94+
.isEqualTo("ISOLATION_READ_COMMITTED"));
95+
}
96+
9597
@EnableBatchProcessing
9698
@TestAutoConfigurationPackage(City.class)
9799
static class DefaultConfiguration {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/JobRepositoryTestingSupport.java

Lines changed: 0 additions & 76 deletions
This file was deleted.

0 commit comments

Comments
 (0)