mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-07 15:39:29 +00:00
Add runtime compatibility with JPA 4.0 M7 and Hibernate ORM 8.0
Closes gh-37315
This commit is contained in:
+19
-3
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.orm.jpa;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -38,6 +39,7 @@ import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
|
||||
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypesScanner;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.FactoryBean} that creates a JPA
|
||||
@@ -76,6 +78,9 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
|
||||
private static final String NON_JTA_DATASOURCE_PROPERTY = "jakarta.persistence.nonJtaDataSource";
|
||||
|
||||
private static final @Nullable Method MANAGED_PACKAGE_DESCRIPTOR_METHOD =
|
||||
ClassUtils.getMethodIfAvailable(PersistenceConfiguration.class, "managedPackageDescriptor", String.class);
|
||||
|
||||
private @Nullable PersistenceConfiguration configuration;
|
||||
|
||||
private String @Nullable [] packagesToScan;
|
||||
@@ -242,18 +247,29 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
|
||||
}
|
||||
|
||||
if (this.packagesToScan != null) {
|
||||
PersistenceConfiguration config = getPersistenceConfiguration();
|
||||
PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(this.resourcePatternResolver);
|
||||
PersistenceManagedTypes result = scanner.scan(this.packagesToScan);
|
||||
|
||||
// Expose managed class names from scan result (on JPA 4.0+, this includes
|
||||
// everything meta-annotated with @Discoverable, even package-info classes)
|
||||
Set<String> classNameSet = new LinkedHashSet<>(result.getManagedClassNames());
|
||||
|
||||
// Expose managed packages as package-info class names if not included already
|
||||
// (accepted by PersistenceConfiguration on Hibernate as well as EclipseLink)
|
||||
for (String managedPackage : result.getManagedPackages()) {
|
||||
classNameSet.add(managedPackage + ClassUtils.PACKAGE_INFO_SUFFIX);
|
||||
if (MANAGED_PACKAGE_DESCRIPTOR_METHOD != null) { // on JPA 4.0
|
||||
for (String managedPackage : result.getManagedPackages()) {
|
||||
ReflectionUtils.invokeMethod(MANAGED_PACKAGE_DESCRIPTOR_METHOD, config, managedPackage);
|
||||
classNameSet.remove(managedPackage + ClassUtils.PACKAGE_INFO_SUFFIX);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (String managedPackage : result.getManagedPackages()) {
|
||||
classNameSet.add(managedPackage + ClassUtils.PACKAGE_INFO_SUFFIX);
|
||||
}
|
||||
}
|
||||
|
||||
// Expose pre-resolved Class references to PersistenceConfiguration.
|
||||
PersistenceConfiguration config = getPersistenceConfiguration();
|
||||
ClassLoader classLoader = this.resourcePatternResolver.getClassLoader();
|
||||
for (String className : classNameSet) {
|
||||
config.managedClass(ClassUtils.resolveClassName(className, classLoader));
|
||||
|
||||
+2
-4
@@ -34,8 +34,6 @@ import org.hibernate.StaleStateException;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.hibernate.UnresolvableObjectException;
|
||||
import org.hibernate.WrongClassException;
|
||||
import org.hibernate.dialect.lock.OptimisticEntityLockException;
|
||||
import org.hibernate.dialect.lock.PessimisticEntityLockException;
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.hibernate.exception.DataException;
|
||||
import org.hibernate.exception.JDBCConnectionException;
|
||||
@@ -206,10 +204,10 @@ public class HibernateExceptionTranslator implements PersistenceExceptionTransla
|
||||
if (exToCheck instanceof StaleStateException) {
|
||||
return new ObjectOptimisticLockingFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
if (exToCheck instanceof OptimisticEntityLockException) {
|
||||
if (exToCheck.getClass().getSimpleName().equals("OptimisticEntityLockException")) {
|
||||
return new ObjectOptimisticLockingFailureException(ex.getMessage(), ex);
|
||||
}
|
||||
if (exToCheck instanceof PessimisticEntityLockException) {
|
||||
if (exToCheck.getClass().getSimpleName().equals("PessimisticEntityLockException")) {
|
||||
if (ex.getCause() instanceof LockAcquisitionException) {
|
||||
return new CannotAcquireLockException(ex.getMessage(), ex.getCause());
|
||||
}
|
||||
|
||||
+29
-3
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.orm.jpa.hibernate;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -24,6 +25,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.hibernate.resource.beans.container.spi.BeanContainer;
|
||||
import org.hibernate.resource.beans.container.spi.ContainedBean;
|
||||
import org.hibernate.resource.beans.spi.BeanInstanceProducer;
|
||||
import org.hibernate.resource.beans.spi.ManagedBean;
|
||||
import org.hibernate.type.spi.TypeBootstrapContext;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -133,6 +135,22 @@ public final class SpringBeanContainer implements BeanContainer {
|
||||
return (SpringContainedBean<B>) bean;
|
||||
}
|
||||
|
||||
// @Override - on Hibernate 8.0
|
||||
public <B> ContainedBean<B> getBootstrapSafeBean(
|
||||
Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
|
||||
|
||||
// Fallback implementation for Hibernate 8.0 runtime compatibility
|
||||
return getBean(beanType, lifecycleOptions, fallbackProducer);
|
||||
}
|
||||
|
||||
// @Override - on Hibernate 8.0
|
||||
public void releaseBean(ManagedBean<?> bean) {
|
||||
if (bean instanceof SpringContainedBean<?> contained) {
|
||||
this.beanCache.values().removeAll(Collections.singleton(contained));
|
||||
contained.destroyIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.beanCache.values().forEach(SpringContainedBean::destroyIfNecessary);
|
||||
@@ -207,9 +225,7 @@ public final class SpringBeanContainer implements BeanContainer {
|
||||
else {
|
||||
// No bean found by name -> construct by type using createBean
|
||||
return new SpringContainedBean<>(
|
||||
beanType,
|
||||
this.beanFactory.createBean(beanType),
|
||||
this.beanFactory::destroyBean);
|
||||
beanType, this.beanFactory.createBean(beanType), this.beanFactory::destroyBean);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -272,6 +288,16 @@ public final class SpringBeanContainer implements BeanContainer {
|
||||
return this.beanClass;
|
||||
}
|
||||
|
||||
// @Override - on Hibernate 8.0
|
||||
public void initialize() {
|
||||
getBeanInstance();
|
||||
}
|
||||
|
||||
// @Override - on Hibernate 8.0
|
||||
public void release() {
|
||||
destroyIfNecessary();
|
||||
}
|
||||
|
||||
public void destroyIfNecessary() {
|
||||
if (this.destructionCallback != null) {
|
||||
this.destructionCallback.accept(this.beanInstance);
|
||||
|
||||
+15
-12
@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.FetchType;
|
||||
@@ -262,21 +263,23 @@ public class SpringPersistenceUnitInfo extends MutablePersistenceUnitInfo {
|
||||
// Fast path for SmartPersistenceUnitInfo JTA check
|
||||
return (getTransactionType() == PersistenceUnitTransactionType.JTA);
|
||||
}
|
||||
else if (method.getName().equals("getAllClassNames")) {
|
||||
// JPA 4.0 letting the container perform the scanning
|
||||
if (excludeUnlistedClasses()) { // typically coming from Spring default persistence unit
|
||||
List<String> mergedClassesAndPackages =
|
||||
new ArrayList<>(getManagedClassNames().size() + getManagedPackages().size());
|
||||
mergedClassesAndPackages.addAll(getManagedClassNames());
|
||||
for (String managedPackage : getManagedPackages()) {
|
||||
mergedClassesAndPackages.add(managedPackage + ClassUtils.PACKAGE_INFO_SUFFIX);
|
||||
}
|
||||
return mergedClassesAndPackages;
|
||||
}
|
||||
|
||||
if (method.getName().startsWith("getAll") && !excludeUnlistedClasses()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"JPA 4.0 getAllClassNames only supported with Spring-configured packagesToScan or " +
|
||||
"JPA 4.0 getAll* methods only supported with Spring-configured packagesToScan or " +
|
||||
"with completely listed managed classes plus exclude-unlisted-classes=true");
|
||||
}
|
||||
switch (method.getName()) {
|
||||
case "getAllClassNames" -> {
|
||||
return getManagedClassNames();
|
||||
}
|
||||
case "getAllPackageDescriptors", "getManagedPackageDescriptors" -> {
|
||||
return getManagedPackages();
|
||||
}
|
||||
case "getAllModuleDescriptors", "getManagedModuleDescriptors" -> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
// Regular methods to be delegated to SpringPersistenceUnitInfo
|
||||
Method targetMethod = SpringPersistenceUnitInfo.class.getMethod(method.getName(), method.getParameterTypes());
|
||||
|
||||
Reference in New Issue
Block a user