Skip to content

Commit 5ab4c5c

Browse files
committed
Add support for JPA 4.0 @PersistenceAgent injection
Closes gh-36264
1 parent c3baa01 commit 5ab4c5c

7 files changed

Lines changed: 235 additions & 63 deletions

File tree

spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,12 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
152152
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = CollectionUtils.newLinkedHashSet(2);
153153

154154
static {
155-
JAKARTA_RESOURCE_TYPE = loadAnnotationType("jakarta.annotation.Resource");
155+
JAKARTA_RESOURCE_TYPE = AnnotationUtils.loadAnnotationType("jakarta.annotation.Resource");
156156
if (JAKARTA_RESOURCE_TYPE != null) {
157157
resourceAnnotationTypes.add(JAKARTA_RESOURCE_TYPE);
158158
}
159159

160-
EJB_ANNOTATION_TYPE = loadAnnotationType("jakarta.ejb.EJB");
160+
EJB_ANNOTATION_TYPE = AnnotationUtils.loadAnnotationType("jakarta.ejb.EJB");
161161
if (EJB_ANNOTATION_TYPE != null) {
162162
resourceAnnotationTypes.add(EJB_ANNOTATION_TYPE);
163163
}
@@ -191,8 +191,8 @@ public CommonAnnotationBeanPostProcessor() {
191191
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
192192

193193
// Jakarta EE 9 set of annotations in jakarta.annotation package
194-
addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct"));
195-
addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy"));
194+
addInitAnnotationType(AnnotationUtils.loadAnnotationType("jakarta.annotation.PostConstruct"));
195+
addDestroyAnnotationType(AnnotationUtils.loadAnnotationType("jakarta.annotation.PreDestroy"));
196196

197197
// java.naming module present on JDK 9+?
198198
if (JNDI_PRESENT) {
@@ -575,18 +575,6 @@ protected Object autowireResource(BeanFactory factory, LookupElement element, @N
575575
}
576576

577577

578-
@SuppressWarnings("unchecked")
579-
private static @Nullable Class<? extends Annotation> loadAnnotationType(String name) {
580-
try {
581-
return (Class<? extends Annotation>)
582-
ClassUtils.forName(name, CommonAnnotationBeanPostProcessor.class.getClassLoader());
583-
}
584-
catch (ClassNotFoundException ex) {
585-
return null;
586-
}
587-
}
588-
589-
590578
/**
591579
* Class representing generic injection information about an annotated field
592580
* or setter method, supporting @Resource and related annotations.
@@ -783,16 +771,13 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be
783771
});
784772
GeneratedMethod generateMethod = generatedClass.getMethods().add("apply", method -> {
785773
method.addJavadoc("Apply resource autowiring.");
786-
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC,
787-
javax.lang.model.element.Modifier.STATIC);
774+
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.STATIC);
788775
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
789776
method.addParameter(this.target, INSTANCE_PARAMETER);
790777
method.returns(this.target);
791-
method.addCode(generateMethodCode(generatedClass.getName(),
792-
generationContext.getRuntimeHints()));
778+
method.addCode(generateMethodCode(generatedClass.getName(), generationContext.getRuntimeHints()));
793779
});
794780
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
795-
796781
registerHints(generationContext.getRuntimeHints());
797782
}
798783

spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet;
3737
import org.springframework.core.annotation.MergedAnnotation.Adapt;
3838
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
39+
import org.springframework.util.ClassUtils;
3940
import org.springframework.util.CollectionUtils;
4041
import org.springframework.util.ConcurrentReferenceHashMap;
4142
import org.springframework.util.ReflectionUtils;
@@ -177,6 +178,23 @@ public static boolean isCandidateClass(Class<?> clazz, String annotationName) {
177178
return true;
178179
}
179180

181+
/**
182+
* Load the specified annotation type, if available.
183+
* @param annotationName the fully-qualified name of the annotation type
184+
* @return the annotation type as a {@code Class}, or {@code null} if not found
185+
* @since 7.1
186+
*/
187+
@SuppressWarnings("unchecked")
188+
public static @Nullable Class<? extends Annotation> loadAnnotationType(String annotationName) {
189+
try {
190+
return (Class<? extends Annotation>)
191+
ClassUtils.forName(annotationName, AnnotationUtils.class.getClassLoader());
192+
}
193+
catch (ClassNotFoundException ex) {
194+
return null;
195+
}
196+
}
197+
180198
/**
181199
* Get a single {@link Annotation} of {@code annotationType} from the supplied
182200
* annotation: either the given annotation itself or a direct meta-annotation

spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@ public void setEntityManagerInterface(@Nullable Class<? extends EntityManager> e
261261
return this.entityManagerInterface;
262262
}
263263

264+
@Override
265+
public @Nullable Class<?> getEntityAgentInterface() {
266+
return this.entityAgentInterface;
267+
}
268+
264269
/**
265270
* Specify the vendor-specific JpaDialect implementation to associate with
266271
* this EntityManagerFactory. This will be exposed through the
@@ -419,8 +424,7 @@ public void afterPropertiesSet() throws PersistenceException {
419424

420425
this.sharedEntityManager = SharedEntityManagerCreator.createSharedEntityManager(this.entityManagerFactory);
421426
if (this.entityAgentInterface != null) {
422-
this.sharedEntityAgent = SharedEntityManagerCreator.createSharedEntityAgent(
423-
this.entityManagerFactory, null, this.entityAgentInterface);
427+
this.sharedEntityAgent = SharedEntityManagerCreator.createSharedEntityAgent(this.entityManagerFactory);
424428
}
425429
}
426430

spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ public interface EntityManagerFactoryInfo {
8383
*/
8484
@Nullable Class<? extends EntityManager> getEntityManagerInterface();
8585

86+
/**
87+
* Return the (potentially vendor-specific) EntityAgent interface
88+
* that this factory's EntityAgents will implement.
89+
* <p>A {@code null} return value suggests that autodetection is supposed
90+
* to happen: either based on a target {@code EntityAgent} instance
91+
* or simply defaulting to {@code jakarta.persistence.EntityAgent}.
92+
* @since 7.1
93+
*/
94+
@Nullable Class<?> getEntityAgentInterface();
95+
8696
/**
8797
* Return the vendor-specific JpaDialect implementation for this
8898
* EntityManagerFactory, or {@code null} if not known.

spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,12 @@ static Object createEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> pr
380380

381381
/**
382382
* Determine the {@code jakarta.persistence.EntityAgent} class.
383-
* @return the {@code EntityAgent} class, or {@code null} if not available
384-
* @since 7.0.4
383+
* <p>Intended for internal adaptation to JPA 3.2 vs 4.0.
384+
* @return the {@code EntityAgent} class (on JPA 4.0),
385+
* or {@code null} if not available (on JPA 3.2)
386+
* @since 7.1
385387
*/
386-
static @Nullable Class<?> getEntityAgentClass() {
388+
public static @Nullable Class<?> getEntityAgentClass() {
387389
return (CREATE_ENTITY_AGENT_METHOD != null ? CREATE_ENTITY_AGENT_METHOD.getReturnType() : null);
388390
}
389391

spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.jspecify.annotations.Nullable;
3939

4040
import org.springframework.transaction.support.TransactionSynchronizationManager;
41+
import org.springframework.util.Assert;
4142
import org.springframework.util.ClassUtils;
4243
import org.springframework.util.CollectionUtils;
4344
import org.springframework.util.ConcurrentReferenceHashMap;
@@ -177,19 +178,38 @@ public static EntityManager createSharedEntityManager(EntityManagerFactory emf,
177178
ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction));
178179
}
179180

181+
/**
182+
* Create a transactional EntityAgent proxy for the given EntityManagerFactory.
183+
* @param emf the EntityManagerFactory to obtain EntityAgents from as needed
184+
* @return a shareable transactional EntityAgent proxy
185+
* (typed to {@code Object} for compatibility with JPA 3.2; this will change
186+
* to {@code EntityAgent} once Spring establishes a JPA 4.0 minimum baseline)
187+
* @since 7.1
188+
*/
189+
public static Object createSharedEntityAgent(EntityManagerFactory emf) {
190+
return createSharedEntityAgent(emf, null);
191+
}
192+
180193
/**
181194
* Create a transactional EntityAgent proxy for the given EntityManagerFactory.
182195
* @param emf the EntityManagerFactory to obtain EntityAgentss from as needed
183196
* @param properties the properties to be passed into the
184197
* {@code createEntityAgent} call (may be {@code null})
185-
* @param ifc the interfaces to be implemented by the EntityAgent
186198
* @return a shareable transactional EntityAgent proxy
187-
* @since 7.0.4
199+
* (typed to {@code Object} for compatibility with JPA 3.2; this will change
200+
* to {@code EntityAgent} once Spring establishes a JPA 4.0 minimum baseline)
201+
* @since 7.1
188202
*/
189-
static Object createSharedEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> properties, Class<?> ifc) {
203+
public static Object createSharedEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> properties) {
190204
ClassLoader cl = null;
205+
Class<?> ifc = null;
191206
if (emf instanceof EntityManagerFactoryInfo emfInfo) {
192207
cl = emfInfo.getBeanClassLoader();
208+
ifc = emfInfo.getEntityAgentInterface();
209+
}
210+
if (ifc == null) {
211+
ifc = EntityManagerFactoryUtils.getEntityAgentClass();
212+
Assert.state(ifc != null, "JPA 4.0 EntityAgent class not found");
193213
}
194214
return Proxy.newProxyInstance(
195215
(cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()),

0 commit comments

Comments
 (0)