Skip to content

Commit a45d438

Browse files
committed
Enforce JPA bootstrap failure for incomplete Hibernate Validator setup
Closes gh-30549
1 parent cabc41b commit a45d438

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

spring-orm/spring-orm.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dependencies {
1111
optional("org.eclipse.persistence:org.eclipse.persistence.jpa")
1212
optional("org.hibernate:hibernate-core-jakarta")
1313
optional("jakarta.servlet:jakarta.servlet-api")
14+
optional("jakarta.validation:jakarta.validation-api")
1415
testImplementation(project(":spring-core-test"))
1516
testImplementation(testFixtures(project(":spring-beans")))
1617
testImplementation(testFixtures(project(":spring-context")))

spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import jakarta.persistence.SharedCacheMode;
3232
import jakarta.persistence.ValidationMode;
3333
import jakarta.persistence.spi.PersistenceUnitInfo;
34+
import jakarta.validation.NoProviderFoundException;
35+
import jakarta.validation.Validation;
3436
import org.apache.commons.logging.Log;
3537
import org.apache.commons.logging.LogFactory;
3638

@@ -48,6 +50,7 @@
4850
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
4951
import org.springframework.jdbc.datasource.lookup.MapDataSourceLookup;
5052
import org.springframework.lang.Nullable;
53+
import org.springframework.util.ClassUtils;
5154
import org.springframework.util.ObjectUtils;
5255
import org.springframework.util.ResourceUtils;
5356

@@ -100,6 +103,9 @@ public class DefaultPersistenceUnitManager
100103
public static final String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME = "default";
101104

102105

106+
private static final boolean beanValidationPresent = ClassUtils.isPresent(
107+
"jakarta.validation.Validation", DefaultPersistenceUnitManager.class.getClassLoader());
108+
103109
protected final Log logger = LogFactory.getLog(getClass());
104110

105111
private String[] persistenceXmlLocations = new String[] {DEFAULT_PERSISTENCE_XML_LOCATION};
@@ -449,7 +455,7 @@ public void preparePersistenceUnitInfos() {
449455
pui.setPersistenceUnitRootUrl(determineDefaultPersistenceUnitRootUrl());
450456
}
451457

452-
// Override DataSource and cache/validation mode
458+
// Override DataSource and shared cache mode
453459
if (pui.getJtaDataSource() == null && this.defaultJtaDataSource != null) {
454460
pui.setJtaDataSource(this.defaultJtaDataSource);
455461
}
@@ -459,9 +465,16 @@ public void preparePersistenceUnitInfos() {
459465
if (this.sharedCacheMode != null) {
460466
pui.setSharedCacheMode(this.sharedCacheMode);
461467
}
468+
469+
// Override validation mode or pre-resolve provider detection
462470
if (this.validationMode != null) {
463471
pui.setValidationMode(this.validationMode);
464472
}
473+
else if (pui.getValidationMode() == ValidationMode.AUTO) {
474+
pui.setValidationMode(
475+
beanValidationPresent && BeanValidationDelegate.isValidationProviderPresent() ?
476+
ValidationMode.CALLBACK : ValidationMode.NONE);
477+
}
465478

466479
// Initialize persistence unit ClassLoader
467480
if (this.loadTimeWeaver != null) {
@@ -697,4 +710,21 @@ public PersistenceUnitInfo obtainPersistenceUnitInfo(String persistenceUnitName)
697710
return pui;
698711
}
699712

713+
714+
/**
715+
* Inner class to avoid a hard dependency on the Bean Validation API at runtime.
716+
*/
717+
private static class BeanValidationDelegate {
718+
719+
public static boolean isValidationProviderPresent() {
720+
try {
721+
Validation.byDefaultProvider().configure();
722+
return true;
723+
}
724+
catch (NoProviderFoundException ex) {
725+
return false;
726+
}
727+
}
728+
}
729+
700730
}

0 commit comments

Comments
 (0)