diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java index 87de3cc71da..94fa087dead 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java @@ -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. + *

NOTE: Only applied if no external PersistenceUnitManager specified. Also, + * this requires a JPA 4.0+ persistence provider and will be ignored otherwise. + * @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 diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java index c014ef43fc1..2bbdf5ca715 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java @@ -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. + *

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) { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java index 5a66df719aa..f5d7ac215da 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java @@ -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); } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java index ab1fe866387..941d9e54916 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java @@ -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); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java index a2819332b68..6141eaa62e7 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/SpringPersistenceUnitInfo.java @@ -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 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()); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/SpringHibernateJpaPersistenceProvider.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/SpringHibernateJpaPersistenceProvider.java index ea7b9988fc0..63fd2a23036 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/SpringHibernateJpaPersistenceProvider.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/SpringHibernateJpaPersistenceProvider.java @@ -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 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 getManagedClassNames() { + return mergedClassesAndPackages; + } + }; + } + else if (ClassUtils.hasMethod(PersistenceUnitInfoDescriptor.class, "isClassTransformerRegistrationDisabled")) { + // Hibernate 8.0: no pushClassTransformer override necessary + descriptor = new PersistenceUnitInfoDescriptor(info) { + @Override + public List 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 getManagedClassNames() { + return mergedClassesAndPackages; + } + @Override + public void pushClassTransformer(EnhancementContext enhancementContext) { + // no-op + } + }; + } + + return new EntityManagerFactoryBuilderImpl(descriptor, properties).build(); } } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java index dddb4596de1..426cbc2dd79 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java @@ -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; + } } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java index 6af57c934ec..fce679328ab 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java @@ -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; + } } }