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:
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user