Runtime compatibility with JPA 4.0.0-M1 and Hibernate ORM 8.0.0.Alpha1

Closes gh-35705
This commit is contained in:
Juergen Hoeller
2026-02-03 19:13:48 +01:00
parent ad19c1ac82
commit ac243fae65
8 changed files with 136 additions and 14 deletions
@@ -21,6 +21,7 @@ import java.util.List;
import javax.sql.DataSource;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.FetchType;
import jakarta.persistence.PersistenceConfiguration;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.SharedCacheMode;
@@ -287,6 +288,17 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage
this.internalPersistenceUnitManager.setValidationMode(validationMode);
}
/**
* Specify the JPA 4.0 default fetch type for all of this manager's persistence
* units, overriding any value in {@code persistence.xml} if set.
* <p><b>NOTE: Only applied if no external PersistenceUnitManager specified. Also,
* this requires a JPA 4.0+ persistence provider and will be ignored otherwise.</b>
* @since 7.0.4
*/
public void setDefaultToOneFetchType(FetchType defaultToOneFetchType) {
this.internalPersistenceUnitManager.setDefaultToOneFetchType(defaultToOneFetchType);
}
/**
* Specify the JDBC DataSource that the JPA persistence provider is supposed
* to use for accessing the database. This is an alternative to keeping the
@@ -27,6 +27,7 @@ import java.util.Set;
import javax.sql.DataSource;
import jakarta.persistence.FetchType;
import jakarta.persistence.PersistenceConfiguration;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.SharedCacheMode;
@@ -123,6 +124,8 @@ public class DefaultPersistenceUnitManager
private @Nullable ValidationMode validationMode;
private @Nullable FetchType defaultToOneFetchType;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private @Nullable DataSource defaultDataSource;
@@ -291,6 +294,17 @@ public class DefaultPersistenceUnitManager
this.validationMode = validationMode;
}
/**
* Specify the JPA 4.0 default fetch type for all of this manager's persistence
* units, overriding any value in {@code persistence.xml} if set.
* <p>This setting only applies against a JPA 4.0+ persistence provider.
* Otherwise, it will be ignored.
* @since 7.0.4
*/
public void setDefaultToOneFetchType(FetchType defaultToOneFetchType) {
this.defaultToOneFetchType = defaultToOneFetchType;
}
/**
* Specify the JDBC DataSources that the JPA persistence provider is supposed
* to use for accessing the database, resolving data source names in
@@ -475,6 +489,9 @@ public class DefaultPersistenceUnitManager
if (this.validationMode != null) {
pui.setValidationMode(this.validationMode);
}
if (this.defaultToOneFetchType != null) {
pui.setDefaultToOneFetchType(this.defaultToOneFetchType);
}
// Initialize persistence unit ClassLoader
if (this.loadTimeWeaver != null) {
@@ -23,6 +23,7 @@ import java.util.Properties;
import javax.sql.DataSource;
import jakarta.persistence.FetchType;
import jakarta.persistence.PersistenceUnitTransactionType;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.ValidationMode;
@@ -82,6 +83,8 @@ public class MutablePersistenceUnitInfo {
private ValidationMode validationMode = ValidationMode.AUTO;
private FetchType defaultToOneFetchType = FetchType.EAGER;
private Properties properties = new Properties();
private String persistenceXMLSchemaVersion = "3.2";
@@ -213,6 +216,14 @@ public class MutablePersistenceUnitInfo {
return this.validationMode;
}
public void setDefaultToOneFetchType(FetchType defaultToOneFetchType) {
this.defaultToOneFetchType = defaultToOneFetchType;
}
public FetchType getDefaultToOneFetchType() {
return this.defaultToOneFetchType;
}
public void addProperty(String name, String value) {
this.properties.setProperty(name, value);
}
@@ -26,6 +26,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import jakarta.persistence.FetchType;
import jakarta.persistence.PersistenceUnitTransactionType;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.ValidationMode;
@@ -85,6 +86,8 @@ final class PersistenceUnitReader {
private static final String VALIDATION_MODE = "validation-mode";
private static final String DEFAULT_TO_ONE_FETCH_TYPE = "default-to-one-fetch-type";
private static final String PROPERTIES = "properties";
private static final String META_INF = "META-INF";
@@ -251,6 +254,12 @@ final class PersistenceUnitReader {
unitInfo.setValidationMode(ValidationMode.valueOf(validationMode));
}
// set JPA 4.0 default fetch type
String fetchType = DomUtils.getChildElementValueByTagName(persistenceUnit, DEFAULT_TO_ONE_FETCH_TYPE);
if (StringUtils.hasText(fetchType)) {
unitInfo.setDefaultToOneFetchType(FetchType.valueOf(fetchType));
}
parseQualifiers(persistenceUnit, unitInfo);
parseMappingFiles(persistenceUnit, unitInfo);
parseJarFiles(persistenceUnit, unitInfo);
@@ -23,6 +23,7 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.FetchType;
import jakarta.persistence.PersistenceConfiguration;
import jakarta.persistence.PersistenceUnitTransactionType;
import jakarta.persistence.spi.ClassTransformer;
@@ -207,6 +208,16 @@ public class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo {
setSharedCacheMode(config.sharedCacheMode());
setValidationMode(config.validationMode());
// JPA 4.0 defaultToOneFetchType
Method defaultToOneFetchType = ClassUtils.getMethodIfAvailable(config.getClass(), "defaultToOneFetchType");
if (defaultToOneFetchType != null) {
FetchType fetchType = (FetchType) ReflectionUtils.invokeMethod(defaultToOneFetchType, config);
if (fetchType != null) {
setDefaultToOneFetchType(fetchType);
}
}
getProperties().putAll(config.properties());
// Further relevant settings from HibernatePersistenceConfiguration on Hibernate 7.1+
@@ -247,10 +258,21 @@ public class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo {
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Fast path for SmartPersistenceUnitInfo JTA check
if (method.getName().equals("isConfiguredForJta")) {
// Fast path for SmartPersistenceUnitInfo JTA check
return (getTransactionType() == PersistenceUnitTransactionType.JTA);
}
else if (method.getName().equals("getAllManagedClassNames")) {
// JPA 4.0 letting the container perform the scanning ->
// with Spring, only makes sense with typical default persistence unit.
if (excludeUnlistedClasses() && getMappingFileNames().isEmpty()) {
List<String> mergedClassesAndPackages = new ArrayList<>(getManagedClassNames());
mergedClassesAndPackages.addAll(getManagedPackages());
return mergedClassesAndPackages;
}
throw new UnsupportedOperationException(
"JPA 4.0 getAllManagedClassNames only supported with exclude-unlisted-classes and no orm.xml");
}
// Regular methods to be delegated to SpringPersistenceUnitInfo
Method targetMethod = SpringPersistenceUnitInfo.class.getMethod(method.getName(), method.getParameterTypes());
@@ -30,6 +30,7 @@ import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
import org.springframework.core.NativeDetector;
import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo;
import org.springframework.util.ClassUtils;
/**
* Spring-specific subclass of the standard {@link HibernatePersistenceProvider}
@@ -51,19 +52,45 @@ class SpringHibernateJpaPersistenceProvider extends HibernatePersistenceProvider
if (info instanceof SmartPersistenceUnitInfo smartInfo) {
mergedClassesAndPackages.addAll(smartInfo.getManagedPackages());
}
return new EntityManagerFactoryBuilderImpl(
new PersistenceUnitInfoDescriptor(info) {
@Override
public List<String> getManagedClassNames() {
return mergedClassesAndPackages;
}
@Override
public void pushClassTransformer(EnhancementContext enhancementContext) {
if (!NativeDetector.inNativeImage()) {
super.pushClassTransformer(enhancementContext);
}
}
}, properties).build();
PersistenceUnitInfoDescriptor descriptor;
if (!NativeDetector.inNativeImage()) {
// No ClassTransformer adaptation necessary
descriptor = new PersistenceUnitInfoDescriptor(info) {
@Override
public List<String> getManagedClassNames() {
return mergedClassesAndPackages;
}
};
}
else if (ClassUtils.hasMethod(PersistenceUnitInfoDescriptor.class, "isClassTransformerRegistrationDisabled")) {
// Hibernate 8.0: no pushClassTransformer override necessary
descriptor = new PersistenceUnitInfoDescriptor(info) {
@Override
public List<String> getManagedClassNames() {
return mergedClassesAndPackages;
}
// @Override on Hibernate 8.0
public boolean isClassTransformerRegistrationDisabled() {
return true;
}
};
}
else {
// Hibernate 7.x: pushClassTransformer no-op in native image
descriptor = new PersistenceUnitInfoDescriptor(info) {
@Override
public List<String> getManagedClassNames() {
return mergedClassesAndPackages;
}
@Override
public void pushClassTransformer(EnhancementContext enhancementContext) {
// no-op
}
};
}
return new EntityManagerFactoryBuilderImpl(descriptor, properties).build();
}
}
@@ -27,6 +27,7 @@ import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceConfiguration;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.PersistenceUnitTransactionType;
import jakarta.persistence.spi.ClassTransformer;
import jakarta.persistence.spi.PersistenceProvider;
import jakarta.persistence.spi.PersistenceUnitInfo;
import jakarta.persistence.spi.ProviderUtil;
@@ -365,6 +366,16 @@ class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerF
public boolean generateSchema(String persistenceUnitName, Map map) {
throw new UnsupportedOperationException();
}
// JPA 4.0 method
public boolean generateSchema(PersistenceConfiguration persistenceConfiguration) {
throw new UnsupportedOperationException();
}
// JPA 4.0 method
public ClassTransformer getClassTransformer(PersistenceUnitInfo persistenceUnitInfo, Map<?,?> map) {
return null;
}
}
@@ -21,6 +21,7 @@ import java.util.Properties;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceConfiguration;
import jakarta.persistence.spi.ClassTransformer;
import jakarta.persistence.spi.PersistenceProvider;
import jakarta.persistence.spi.PersistenceUnitInfo;
import jakarta.persistence.spi.ProviderUtil;
@@ -184,15 +185,27 @@ class LocalEntityManagerFactoryBeanTests extends AbstractEntityManagerFactoryBea
throw new UnsupportedOperationException();
}
// JPA 2.1 method
@Override
public void generateSchema(PersistenceUnitInfo persistenceUnitInfo, Map map) {
throw new UnsupportedOperationException();
}
// JPA 2.1 method
@Override
public boolean generateSchema(String persistenceUnitName, Map map) {
throw new UnsupportedOperationException();
}
// JPA 4.0 method
public boolean generateSchema(PersistenceConfiguration persistenceConfiguration) {
throw new UnsupportedOperationException();
}
// JPA 4.0 method
public ClassTransformer getClassTransformer(PersistenceUnitInfo persistenceUnitInfo, Map<?,?> map) {
return null;
}
}
}