Browse Source

Replace PropertyBasedRequiredBackgroundBootstrapping with exception

Replace `PropertyBasedRequiredBackgroundBootstrapping` with a simpler
`BootstrapExecutorRequiredException` and use a regular failure analyzer
to provide help.

See gh-49733
See gh-49688
pull/49787/head
Phillip Webb 1 week ago
parent
commit
d28cec064e
  1. 6
      module/spring-boot-data-jpa/src/main/java/org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration.java
  2. 6
      module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java
  3. 69
      module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/BootstrapExecutorRequiredException.java
  4. 50
      module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/BootstrapExecutorRequiredFailureAnalyzer.java
  5. 2
      module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java
  6. 60
      module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/PropertyBasedRequiredBackgroundBootstrapping.java
  7. 4
      module/spring-boot-jpa/src/main/resources/META-INF/spring.factories
  8. 66
      module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/autoconfigure/BootstrapExecutorRequiredFailureAnalyzerTests.java
  9. 40
      module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/autoconfigure/PropertyBasedRequiredBackgroundBootstrappingTests.java

6
module/spring-boot-data-jpa/src/main/java/org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration.java

@ -33,8 +33,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -33,8 +33,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.data.jpa.autoconfigure.DataJpaRepositoriesAutoConfiguration.JpaRepositoriesImportSelector;
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration;
import org.springframework.boot.jpa.autoconfigure.BootstrapExecutorRequiredException;
import org.springframework.boot.jpa.autoconfigure.EntityManagerFactoryBuilderCustomizer;
import org.springframework.boot.jpa.autoconfigure.PropertyBasedRequiredBackgroundBootstrapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
@ -85,8 +85,8 @@ public final class DataJpaRepositoriesAutoConfiguration { @@ -85,8 +85,8 @@ public final class DataJpaRepositoriesAutoConfiguration {
@ConditionalOnProperty(name = "spring.data.jpa.repositories.bootstrap-mode", havingValue = "deferred")
EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer(
Map<String, AsyncTaskExecutor> taskExecutors) {
return (builder) -> builder.requireBootstrapExecutor(new PropertyBasedRequiredBackgroundBootstrapping(
"spring.data.jpa.repositories.bootstrap-mode", "deferred"));
return (builder) -> builder.requireBootstrapExecutor(() -> BootstrapExecutorRequiredException
.ofProperty("spring.data.jpa.repositories.bootstrap-mode", "deferred"));
}
@Bean

6
module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java

@ -68,7 +68,6 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -68,7 +68,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.diagnostics.FailureAnalyzedException;
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
import org.springframework.boot.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfigurationTests.JpaUsingApplicationListenerConfiguration.EventCapturingApplicationListener;
@ -82,6 +81,7 @@ import org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoC @@ -82,6 +81,7 @@ import org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoC
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.XADataSourceAutoConfiguration;
import org.springframework.boot.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.jpa.autoconfigure.BootstrapExecutorRequiredException;
import org.springframework.boot.jpa.autoconfigure.EntityManagerFactoryBuilderCustomizer;
import org.springframework.boot.jpa.autoconfigure.JpaBaseConfiguration;
import org.springframework.boot.jpa.autoconfigure.JpaProperties;
@ -274,7 +274,7 @@ class HibernateJpaAutoConfigurationTests { @@ -274,7 +274,7 @@ class HibernateJpaAutoConfigurationTests {
this.contextRunner.withPropertyValues("spring.jpa.bootstrap=async")
.run((context) -> assertThat(context).getFailure()
.rootCause()
.isInstanceOf(FailureAnalyzedException.class)
.isInstanceOf(BootstrapExecutorRequiredException.class)
.message()
.contains("bootstrap executor is required when 'spring.jpa.bootstrap' is set to 'async'"));
}
@ -285,7 +285,7 @@ class HibernateJpaAutoConfigurationTests { @@ -285,7 +285,7 @@ class HibernateJpaAutoConfigurationTests {
.withUserConfiguration(MultipleAsyncTaskExecutorsConfiguration.class)
.run((context) -> assertThat(context).getFailure()
.rootCause()
.isInstanceOf(FailureAnalyzedException.class)
.isInstanceOf(BootstrapExecutorRequiredException.class)
.message()
.contains("bootstrap executor is required when 'spring.jpa.bootstrap' is set to 'async'"));
}

69
module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/BootstrapExecutorRequiredException.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jpa.autoconfigure;
import org.jspecify.annotations.Nullable;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.Assert;
/**
* Exception thrown then the auto-configured
* {@link LocalContainerEntityManagerFactoryBean} is missing a required bootstrap
* executor.
*
* @author Phillip Webb
* @since 4.1.0
*/
public class BootstrapExecutorRequiredException extends IllegalStateException {
private final @Nullable String propertyName;
private final @Nullable String propertyValue;
public BootstrapExecutorRequiredException(String message) {
this(message, null);
}
public BootstrapExecutorRequiredException(String message, @Nullable Throwable cause) {
this(message, null, null, cause);
}
private BootstrapExecutorRequiredException(String message, @Nullable String propertyName,
@Nullable String propertyValue, @Nullable Throwable cause) {
super(message, cause);
this.propertyName = propertyName;
this.propertyValue = propertyValue;
}
@Nullable String getPropertyName() {
return this.propertyName;
}
@Nullable String getPropertyValue() {
return this.propertyValue;
}
public static BootstrapExecutorRequiredException ofProperty(String name, String value) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(value, "'value' must not be empty");
String message = "An EntityManagerFactoryBean bootstrap executor is required when '%s' is set to '%s'"
.formatted(name, value);
return new BootstrapExecutorRequiredException(message, name, value, null);
}
}

50
module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/BootstrapExecutorRequiredFailureAnalyzer.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jpa.autoconfigure;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
/**
* {@link FailureAnalyzer} for {@link BootstrapExecutorRequiredException}.
*
* @author Phillip Webb
*/
class BootstrapExecutorRequiredFailureAnalyzer extends AbstractFailureAnalyzer<BootstrapExecutorRequiredException> {
@Override
protected @Nullable FailureAnalysis analyze(Throwable rootFailure, BootstrapExecutorRequiredException cause) {
StringBuilder action = new StringBuilder();
action.append(actionPreamble(cause.getPropertyName()));
action.append("\t- With an auto-configured task executor "
+ "(you may need to set 'spring.task.execution.mode' to 'force').\n");
action.append("\t- With an AsyncTaskExecutor bean named 'applicationTaskExecutor'.\n");
action.append("\t- Using a EntityManagerFactoryBuilderCustomizer.\n");
return new FailureAnalysis(cause.getMessage(), action.toString(), cause);
}
private String actionPreamble(@Nullable String propertyName) {
return (propertyName != null)
? "Use a different '%s' value or provide a bootstrap executor using one of the following methods:\n"
.formatted(propertyName)
: "Provide a bootstrap executor using one of the following methods:\n";
}
}

2
module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java

@ -130,7 +130,7 @@ public abstract class JpaBaseConfiguration { @@ -130,7 +130,7 @@ public abstract class JpaBaseConfiguration {
this::buildJpaProperties, persistenceUnitManager.getIfAvailable(), null, bootstrapExecutor);
if (this.properties.getBootstrap() == Bootstrap.ASYNC) {
builder.requireBootstrapExecutor(
new PropertyBasedRequiredBackgroundBootstrapping("spring.jpa.bootstrap", "async"));
() -> BootstrapExecutorRequiredException.ofProperty("spring.jpa.bootstrap", "async"));
}
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;

60
module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/PropertyBasedRequiredBackgroundBootstrapping.java

@ -1,60 +0,0 @@ @@ -1,60 +0,0 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jpa.autoconfigure;
import java.util.function.Supplier;
import org.springframework.boot.diagnostics.FailureAnalyzedException;
import org.springframework.boot.jpa.EntityManagerFactoryBuilder;
import org.springframework.util.Assert;
/**
* {@link Supplier} to use with
* {@link EntityManagerFactoryBuilder#requireBootstrapExecutor} when a property indicates
* background bootstrapping is required.
*
* @author Phillip Webb
* @since 4.1.0
*/
public class PropertyBasedRequiredBackgroundBootstrapping implements Supplier<RuntimeException> {
private final String propertyName;
private final String propertyValue;
public PropertyBasedRequiredBackgroundBootstrapping(String propertyName, String propertyValue) {
Assert.notNull(propertyName, "'propertyName' must not be null");
Assert.notNull(propertyValue, "'propertyValue' must not be null");
this.propertyName = propertyName;
this.propertyValue = propertyValue;
}
@Override
public RuntimeException get() {
String description = "A LocalContainerEntityManagerFactoryBean bootstrap executor is required when '%s' is set to '%s'"
.formatted(this.propertyName, this.propertyValue);
StringBuilder action = new StringBuilder();
action.append("Use a different '%s' or provide a bootstrap executor using one of the following methods:\n"
.formatted(this.propertyName));
action.append("\tWith an auto-configured task executor "
+ "(you may need to set 'spring.task.execution.mode' to 'force').");
action.append("\tWith an AsyncTaskExecutor bean named 'applicationTaskExecutor.'");
action.append("\tUsing a EntityManagerFactoryBuilderCustomizer.");
return new FailureAnalyzedException(description, action.toString());
}
}

4
module/spring-boot-jpa/src/main/resources/META-INF/spring.factories

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.jpa.autoconfigure.BootstrapExecutorRequiredFailureAnalyzer
# Database Initializer Detectors
org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector=\
org.springframework.boot.jpa.JpaDatabaseInitializerDetector

66
module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/autoconfigure/BootstrapExecutorRequiredFailureAnalyzerTests.java

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jpa.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.diagnostics.FailureAnalysis;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link BootstrapExecutorRequiredFailureAnalyzer}.
*
* @author Phillip Webb
*/
class BootstrapExecutorRequiredFailureAnalyzerTests {
private final BootstrapExecutorRequiredFailureAnalyzer analyzer = new BootstrapExecutorRequiredFailureAnalyzer();
@Test
void analyzeWhenBootstrapExecutorRequiredExceptionWithProperties() {
BootstrapExecutorRequiredException exception = BootstrapExecutorRequiredException.ofProperty("testname",
"testvalue");
FailureAnalysis result = this.analyzer.analyze(exception);
assertThat(result).isNotNull();
assertThat(result.getDescription()).isEqualTo(
"An EntityManagerFactoryBean bootstrap executor is required when 'testname' is set to 'testvalue'");
assertThat(result.getAction()).isEqualTo(
"""
Use a different 'testname' value or provide a bootstrap executor using one of the following methods:
- With an auto-configured task executor (you may need to set 'spring.task.execution.mode' to 'force').
- With an AsyncTaskExecutor bean named 'applicationTaskExecutor'.
- Using a EntityManagerFactoryBuilderCustomizer.
""");
}
@Test
void analyzeWhenBootstrapExecutorRequiredExceptionWithMessage() {
BootstrapExecutorRequiredException exception = new BootstrapExecutorRequiredException("A custom message");
FailureAnalysis result = this.analyzer.analyze(exception);
assertThat(result).isNotNull();
assertThat(result.getDescription()).isEqualTo("A custom message");
assertThat(result.getAction()).isEqualTo(
"""
Provide a bootstrap executor using one of the following methods:
- With an auto-configured task executor (you may need to set 'spring.task.execution.mode' to 'force').
- With an AsyncTaskExecutor bean named 'applicationTaskExecutor'.
- Using a EntityManagerFactoryBuilderCustomizer.
""");
}
}

40
module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/autoconfigure/PropertyBasedRequiredBackgroundBootstrappingTests.java

@ -1,40 +0,0 @@ @@ -1,40 +0,0 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jpa.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.diagnostics.FailureAnalyzedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PropertyBasedRequiredBackgroundBootstrapping}.
*
* @author Phillip Webb
*/
class PropertyBasedRequiredBackgroundBootstrappingTests {
@Test
void getReturnsFailureAnalyzableException() {
RuntimeException exception = new PropertyBasedRequiredBackgroundBootstrapping("test.bootstrap", "true").get();
assertThat(exception).isInstanceOf(FailureAnalyzedException.class)
.hasMessage("A LocalContainerEntityManagerFactoryBean bootstrap executor is required "
+ "when 'test.bootstrap' is set to 'true'");
}
}
Loading…
Cancel
Save