Merge pull request #28859 from tiborsulyan

* pr/28859:
  Polish "Add option to allow Spring Batch custom isolation levels"
  Add option to allow Spring Batch custom isolation levels

Closes gh-28859
This commit is contained in:
Stephane Nicoll
2022-01-11 11:08:26 +01:00
5 changed files with 109 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.batch.BatchProperties.Isolation;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@@ -139,7 +140,8 @@ public class BasicBatchConfigurer implements BatchConfigurer, InitializingBean {
* @return the isolation level or {@code null} to use the default
*/
protected String determineIsolationLevel() {
return null;
Isolation isolation = this.properties.getJdbc().getIsolationLevelForCreate();
return (isolation != null) ? isolation.toIsolationName() : null;
}
protected PlatformTransactionManager createTransactionManager() {

View File

@@ -66,6 +66,12 @@ public class BatchProperties {
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/"
+ "batch/core/schema-@@platform@@.sql";
/**
* Transaction isolation level to use when creating job meta-data for new jobs.
* Auto-detected based on whether JPA is being used or not.
*/
private Isolation isolationLevelForCreate;
/**
* Path to the SQL file to use to initialize the database schema.
*/
@@ -87,6 +93,14 @@ public class BatchProperties {
*/
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
public Isolation getIsolationLevelForCreate() {
return this.isolationLevelForCreate;
}
public void setIsolationLevelForCreate(Isolation isolationLevelForCreate) {
this.isolationLevelForCreate = isolationLevelForCreate;
}
public String getSchema() {
return this.schema;
}
@@ -121,4 +135,45 @@ public class BatchProperties {
}
/**
* Available transaction isolation levels.
*/
public enum Isolation {
/**
* Use the default isolation level of the underlying datastore.
*/
DEFAULT,
/**
* Indicates that dirty reads, non-repeatable reads and phantom reads can occur.
*/
READ_UNCOMMITTED,
/**
* Indicates that dirty reads are prevented; non-repeatable reads and phantom
* reads can occur.
*/
READ_COMMITTED,
/**
* Indicates that dirty reads and non-repeatable reads are prevented; phantom
* reads can occur.
*/
REPEATABLE_READ,
/**
* Indicate that dirty reads, non-repeatable reads and phantom reads are
* prevented.
*/
SERIALIZABLE;
private static final String PREFIX = "ISOLATION_";
String toIsolationName() {
return PREFIX + name();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.batch.BatchProperties.Isolation;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@@ -54,8 +55,15 @@ public class JpaBatchConfigurer extends BasicBatchConfigurer {
@Override
protected String determineIsolationLevel() {
logger.warn("JPA does not support custom isolation levels, so locks may not be taken when launching Jobs");
return "ISOLATION_DEFAULT";
String name = super.determineIsolationLevel();
if (name != null) {
return name;
}
else {
logger.warn("JPA does not support custom isolation levels, so locks may not be taken when launching Jobs. "
+ "To silence this warning, set 'spring.batch.jdbc.isolation-level-for-create' to 'default'.");
return Isolation.DEFAULT.toIsolationName();
}
}
@Override

View File

@@ -23,6 +23,7 @@ import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
@@ -57,6 +58,8 @@ import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@@ -78,6 +81,7 @@ import static org.mockito.Mockito.mock;
* @author Vedran Pavic
* @author Kazuki Shimizu
*/
@ExtendWith(OutputCaptureExtension.class)
class BatchAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@@ -211,6 +215,30 @@ class BatchAutoConfigurationTests {
});
}
@Test
void testDefaultIsolationLevelWithJpaLogsWarning(CapturedOutput output) {
this.contextRunner.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
HibernateJpaAutoConfiguration.class).run((context) -> {
assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel())
.isEqualTo("ISOLATION_DEFAULT");
assertThat(output).contains("JPA does not support custom isolation levels")
.contains("set 'spring.batch.jdbc.isolation-level-for-create' to 'default'");
});
}
@Test
void testCustomIsolationLevelWithJpaDoesNotLogWarning(CapturedOutput output) {
this.contextRunner.withPropertyValues("spring.batch.jdbc.isolation-level-for-create=default")
.withUserConfiguration(TestConfiguration.class, EmbeddedDataSourceConfiguration.class,
HibernateJpaAutoConfiguration.class)
.run((context) -> {
assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel())
.isEqualTo("ISOLATION_DEFAULT");
assertThat(output).doesNotContain("JPA does not support custom isolation levels")
.doesNotContain("set 'spring.batch.jdbc.isolation-level-for-create' to 'default'");
});
}
@Test
void testRenamePrefix() {
this.contextRunner

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -61,6 +61,7 @@ class BatchAutoConfigurationWithoutJpaTests {
.contains("DataSourceTransactionManager");
assertThat(context.getBean(BatchProperties.class).getJdbc().getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel()).isNull();
assertThat(new JdbcTemplate(context.getBean(DataSource.class))
.queryForList("select * from BATCH_JOB_EXECUTION")).isEmpty();
assertThat(context.getBean(JobExplorer.class).findRunningJobExecutions("test")).isEmpty();
@@ -84,6 +85,15 @@ class BatchAutoConfigurationWithoutJpaTests {
});
}
@Test
void jdbcWithCustomIsolationLevel() {
this.contextRunner.withUserConfiguration(DefaultConfiguration.class, EmbeddedDataSourceConfiguration.class)
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.batch.jdbc.isolation-level-for-create=read_committed")
.run((context) -> assertThat(context.getBean(BasicBatchConfigurer.class).determineIsolationLevel())
.isEqualTo("ISOLATION_READ_COMMITTED"));
}
@EnableBatchProcessing
@TestAutoConfigurationPackage(City.class)
static class DefaultConfiguration {