This is the 2nd article of a serie on Spring code analysis.
Today we are going to dig into the @PersistenceContext annotation, widely used by JPA/Hibernate developers to inject an Entity Manager into their DAO classes.
If you’ve missed the first article about Spring @Transactional, have a look here: Spring @Transactional explained
Note: the following code analysis was done with Spring 3.0.5 official release We only focus on the J2SE environment (no EJB) and on the JPA API for database management. However, the code analyzed is generic enough to apply, to some extent, to other cases (J2EE platform, specific vendor JPA API…)
I Usage & use cases
@PersistenceContext(unitName = "myPersistenceUnit") private EntityManager entityManager; or @PersistenceUnit(unitName = "myPersistenceUnit") private EntityManagerManager entityManagerManager;
- USE CASE 1: within no @Transactional method
@PersistenceContext(unitName = "myPersistenceUnit") private EntityManager entityManager; public void querySomething() { this.entityManager.createQuery(“some HQL”); … }
@PersistenceContext(unitName = "myPersistenceUnit") private EntityManager entityManager; @Transactional(value = "myTransactionManager",propagation = Propagation.REQUIRED) public void querySomething() { this.entityManager.createQuery(“some HQL”); … }
@Transactional(value = "myTransactionManager",propagation = Propagation.REQUIRED) public void querySomething() { this.entityManager.createQuery(“some HQL”); … this.injectedBean.transactionalMethod(parameters); } ... public class InjectedBean { @PersistenceContext(unitName = "oracleNoCachePersistenceUnit") private EntityManager entityManager; @Transactional(value = "myTransactionManager",propagation = Propagation.REQUIRED) public void transactionalMethod(…parameters) { this.entityManager.createQuery(“some other HQL”); }
II Registration in Spring context
<context:annotation-config/>
or
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
III Code analysis
A Bean registration
1) org.springframework.context.annotation.ComponentScanBeanDefinitionParser
private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config"; ... protected void registerComponents(XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) { Object source = readerContext.extractSource(element); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source); for (BeanDefinitionHolder beanDefHolder : beanDefinitions) { compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder)); } // Register annotation config processors, if necessary. boolean annotationConfig = true; if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) { annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE)); } if (annotationConfig) { Set<BeanDefinitionHolder> processorDefinitions = // (see Point A) AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source); for (BeanDefinitionHolder processorDefinition : processorDefinitions) { compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition)); } } readerContext.fireComponentRegistered(compositeDef); }
The registration of is delegated to AnnotationConfigUtils.registerAnnotationConfigProcessors()
2) org.springframework.context.annotation.AnnotationConfigUtils
Point A
private static final String PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME = "org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"; /* * Check for JPA support * * JPA is present if classes * * 1) "org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" * 2) "javax.persistence.EntityManagerFactory" * * are detected in the Spring context */ private static final boolean jpaPresent = ClassUtils.isPresent("javax.persistence.EntityManagerFactory", AnnotationConfigUtils.class.getClassLoader()) && ClassUtils.isPresent(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, AnnotationConfigUtils.class.getClassLoader()); // Point A public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, Object source) { Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4); if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } … // Check for JPA support and if present add the PersistenceAnnotationBeanPostProcessor // (if not declared explicitely) if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { ClassLoader cl = AnnotationConfigUtils.class.getClassLoader(); def.setBeanClass(cl.loadClass(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME)); } catch (ClassNotFoundException ex) { throw new IllegalStateException("Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } return beanDefs; }
The registration is done here at lines 47, 53 & 54
B Annotation processing
1) org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { // (see Point B below) InjectionMetadata metadata = findPersistenceMetadata(bean.getClass()); try { // (see Point C) metadata.inject(bean, beanName, pvs); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of persistence dependencies failed", ex); } return pvs; } // Point B private InjectionMetadata findPersistenceMetadata(final Class clazz) { ... // Not interesting code ... do { LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<InjectionMetadata.InjectedElement>(); for (Field field : targetClass.getDeclaredFields()) { PersistenceContext pc = field.getAnnotation(PersistenceContext.class); PersistenceUnit pu = field.getAnnotation(PersistenceUnit.class); if (pc != null || pu != null) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("Persistence annotations are not supported on static fields"); } currElements.add(new PersistenceElement(field, null)); } } for (Method method : targetClass.getDeclaredMethods()) { PersistenceContext pc = method.getAnnotation(PersistenceContext.class); PersistenceUnit pu = method.getAnnotation(PersistenceUnit.class); if (pc != null || pu != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (Modifier.isStatic(method.getModifiers())) { throw new IllegalStateException("Persistence annotations are not supported on static methods"); } if (method.getParameterTypes().length != 1) { throw new IllegalStateException("Persistence annotation requires a single-arg method: " + method); } PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method); currElements.add(new PersistenceElement(method, pd)); } } elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); metadata = new InjectionMetadata(clazz, elements); this.injectionMetadataCache.put(clazz, metadata); ... return metadata;
The method postProcessPropertyValues() is called when the host bean is initialized because the PersistenceAnnotationBeanPostProcessor implements the InstantiationAwareBeanPostProcessor interface
In findPersistenceMetadata(), the most interesting points are at lines 32 & 51 where Spring adds new PersistenceElement instances to the list of InjectionMetaData
2) org.springframework.beans.factory.annotation.InjectionMetadata
Point C
// Point C public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable { if (!this.injectedElements.isEmpty()) { boolean debug = logger.isDebugEnabled(); for (InjectedElement element : this.injectedElements) { if (debug) { logger.debug("Processing injected method of bean '" + beanName + "': " + element); } // (see Point D below) element.inject(target, beanName, pvs); } } } public static abstract class InjectedElement { ... // Point D protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable { if (this.isField) { Field field = (Field) this.member; ReflectionUtils.makeAccessible(field); field.set(target, getResourceToInject(target, requestingBeanName)); } else { if (checkPropertySkipping(pvs)) { return; } try { Method method = (Method) this.member; ReflectionUtils.makeAccessible(method); // (see Point E) method.invoke(target, getResourceToInject(target, requestingBeanName)); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } } }
This class is just a code indirection to call getResourceToInject() on the PersistenceElement
3) org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor
Point E
private class PersistenceElement extends InjectionMetadata.InjectedElement // Point E protected Object getResourceToInject(Object target, String requestingBeanName) { /* * The type attribute(TRANSACTION or EXTENDED) is only available * for @PersistenceContext annotation * * Default type value is PersistenceContextType.TRANSACTION */ if (this.type != null) { return (this.type == PersistenceContextType.EXTENDED ? resolveExtendedEntityManager(target, requestingBeanName) : //See Point F below resolveEntityManager(requestingBeanName)); // See Point G below } // Case of @PersistenceUnit annotation else { // OK, so we need an EntityManagerFactory... return resolveEntityManagerFactory(requestingBeanName); // See Point H below } } // Point F private EntityManager resolveExtendedEntityManager(Object target, String requestingBeanName) { // Obtain EntityManager reference from JNDI? // return a non null em only in a J2EE context EntityManager em = getPersistenceContext(this.unitName, true); if (em == null) { // No pre-built EntityManager found -> build one based on factory. // Obtain EntityManagerFactory from JNDI? // return a non null emf only in a J2EE context EntityManagerFactory emf = getPersistenceUnit(this.unitName); if (emf == null) { // Need to search for EntityManagerFactory beans. emf = findEntityManagerFactory(this.unitName, requestingBeanName); } // Inject a container-managed extended EntityManager. // (see Point I) em = ExtendedEntityManagerCreator.createContainerManagedEntityManager(emf, this.properties); } if (em instanceof EntityManagerProxy && beanFactory != null && !beanFactory.isPrototype(requestingBeanName)) { extendedEntityManagersToClose.put(target, ((EntityManagerProxy) em).getTargetEntityManager()); } return em; } // Point G private EntityManager resolveEntityManager(String requestingBeanName) { // Obtain EntityManager reference from JNDI? // return a non null em only in a J2EE context EntityManager em = getPersistenceContext(this.unitName, false); if (em == null) { // No pre-built EntityManager found -> build one based on factory. // Obtain EntityManagerFactory from JNDI? // return a non null emf only in a J2EE context EntityManagerFactory emf = getPersistenceUnit(this.unitName); if (emf == null) { // Need to search for EntityManagerFactory beans. emf = findEntityManagerFactory(this.unitName, requestingBeanName); } // Inject a shared transactional EntityManager proxy. if (emf instanceof EntityManagerFactoryInfo && ((EntityManagerFactoryInfo) emf).getEntityManagerInterface() != null) { // Create EntityManager based on the info's vendor-specific type // (which might be more specific than the field's type). // (see Point J) em = SharedEntityManagerCreator.createSharedEntityManager(emf, this.properties); } else { // Create EntityManager based on the field's type. em = SharedEntityManagerCreator.createSharedEntityManager(emf, this.properties, getResourceType()); } } return em; } // Point H private EntityManagerFactory resolveEntityManagerFactory(String requestingBeanName) { // Obtain EntityManagerFactory from JNDI? EntityManagerFactory emf = getPersistenceUnit(this.unitName); if (emf == null) { // Need to search for EntityManagerFactory beans. emf = findEntityManagerFactory(this.unitName, requestingBeanName); } return emf; }
Analysis of getResourceToInject() method:
- If the type attribute is not null, we are facing a @PersistenceContext annotation. Two possible values for the type: TRANSACTION (default value) and EXTENDED
- If type = TRANSACTION, call resolveEntityManager() (line 15)
- Else (type = EXTENDED) call resolveExtendedEntityManager() (line 14)
- Else we are facing a @PersistenceUnit annotation, call resolveEntityManagerFactory (line 21)
For both resolveEntityManager() & resolveExtendedEntityManager() methods:
- getPersistenceContext() returns a null object in non J2EE platform
- getPersistenceUnit() returns a null object in non J2EE platform
- findEntityManagerFactory() returns the entity manager factory associated to declared unitName
- If the unitName attribute of the @PersistenceContext/@PersistenceUnit was specified, use it to look up the entity manager factory
- Else find the default entity manager factory (name = “entityManagerFactory” by convention)
- In the EXTENDED @PersistenceContext case, call ExtendedEntityManagerCreator.createContainerManagedEntityManager() (lines 40 & 41)
- In the default @PersistenceContext case, call SharedEntityManagerCreator.createSharedEntityManager() (lines 70 & 71)
- In the @PersistenceUnit case, call findEntityManagerFactory()
4) org.springframework.orm.jpa.ExtendedEntityManagerCreator
Point I
// Point I public static EntityManager createContainerManagedEntityManager(EntityManagerFactory emf, Map properties) { Assert.notNull(emf, "EntityManagerFactory must not be null"); if (emf instanceof EntityManagerFactoryInfo) { EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf; EntityManagerFactory nativeEmf = emfInfo.getNativeEntityManagerFactory(); EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? nativeEmf.createEntityManager(properties) : nativeEmf.createEntityManager()); return createProxy(rawEntityManager, emfInfo, true); } else { EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager()); return createProxy(rawEntityManager, null, null, null, null, null, true); } }
This class is quite straighforward. It creates a new EntityManager instance using the entity manager factory and returns it (lines 8 & 13).
5) org.springframework.orm.jpa.SharedEntityManagerCreator
Point J, Point K & Point L
// Point J public static EntityManager createSharedEntityManager(EntityManagerFactory emf, Map properties) { Class[] emIfcs; ... // Not interesting code ... return createSharedEntityManager(emf, properties, emIfcs); // See Point K below } // Point K public static EntityManager createSharedEntityManager( EntityManagerFactory emf, Map properties, Class... entityManagerInterfaces) { ClassLoader cl = null; if (emf instanceof EntityManagerFactoryInfo) { cl = ((EntityManagerFactoryInfo) emf).getBeanClassLoader(); } Class[] ifcs = new Class[entityManagerInterfaces.length + 1]; System.arraycopy(entityManagerInterfaces, 0, ifcs, 0, entityManagerInterfaces.length); ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class; // Returns a proxy of EntityManager with a SharedEntityManagerInvocationHandler as callback return (EntityManager) Proxy.newProxyInstance( (cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()), ifcs, new SharedEntityManagerInvocationHandler(emf, properties)); // See Point L below } // Point L private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable { ... // Not interesting code ... public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... // Not interesting code ... else if (method.getName().equals("toString")) { // Deliver toString without touching a target EntityManager. return "Shared EntityManager proxy for target factory [" + this.targetFactory + "]"; } else if (method.getName().equals("getEntityManagerFactory")) { // JPA 2.0: return EntityManagerFactory without creating an EntityManager. return this.targetFactory; } ... // Not interesting code ... else if (method.getName().equals("getTransaction")) { throw new IllegalStateException("Not allowed to create transaction on shared" +" EntityManager - use Spring transactions or EJB CMT instead"); } else if (method.getName().equals("joinTransaction")) { throw new IllegalStateException("Not allowed to join transaction on shared " +" EntityManager - use Spring transactions or EJB CMT instead"); } // Determine current EntityManager: either the transactional one // managed by the factory or a temporary one for the given invocation. // (see Point M) EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager( this.targetFactory, this.properties); ... // Not interesting code ... // Regular EntityManager operations. boolean isNewEm = false; // USE CASE 1: @PersistenceContext used with NON @Transactional method if (target == null) { logger.debug("Creating new EntityManager for shared EntityManager invocation"); target = (!CollectionUtils.isEmpty(this.properties) ? this.targetFactory.createEntityManager(this.properties) : this.targetFactory.createEntityManager()); isNewEm = true; } // Invoke method on current EntityManager. try { Object result = method.invoke(target, args); ... // Not interesting code ... catch (InvocationTargetException ex) { throw ex.getTargetException(); } finally { if (isNewEm) { EntityManagerFactoryUtils.closeEntityManager(target); } } }
Many interesting points in this class
- Lines 23, 24 & 25, Spring wraps a proxy object around the class SharedEntityManagerInvocationHandler
- In the SharedEntityManagerInvocationHandler.invoke() method:
- getTransaction() is not allowed because the transaction is managed by Spring using @Transactional annotation (lines 50, 51 & 52)
- Similarly, joinTransaction() is not allowed (lines 55, 56 & 57)
- The entityManager is retrieved by calling EntityManagerFactoryUtils.doGetTransactionalEntityManager()
- If the retrived entityManager is null, it corresponds to USE CASE 1 where the @PersistenceContext is used within NON @Transactional method. In this case Spring will create a new EntityManager instance from scratch (lines 77 & 78)
- At the end of the method invocation, if the EntityManager was created from scratch, it is automatically closed by Spring (lines 94 & 95). This is the session-per-request pattern
6) org.springframework.orm.jpa.EntityManagerFactoryUtils
Point M
// Point M public static EntityManager doGetTransactionalEntityManager( EntityManagerFactory emf, Map properties) throws PersistenceException { Assert.notNull(emf, "No EntityManagerFactory specified"); // Try to get existing EntityManagerHolder from the TransactionSynchronizationManager // ThreadLocal map using the EntityManagerFactory as search key EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf); if (emHolder != null) { if (!emHolder.isSynchronizedWithTransaction() && TransactionSynchronizationManager.isSynchronizationActive()) { // Try to explicitly synchronize the EntityManager itself // with an ongoing JTA transaction, if any. try { // CASE 3 @PersistenceContext used within nested @Transactional methods // Join existing transaction, do not create a new EntityManager // (when @Transactional method is calling other @Transactional methods) emHolder.getEntityManager().joinTransaction(); } catch (TransactionRequiredException ex) { logger.debug("Could not join JTA transaction because none was active", ex); } ... // Not interesting code ... return emHolder.getEntityManager(); } // USE CASE 1: @PersistenceContext used with NON @Transactional method if (!TransactionSynchronizationManager.isSynchronizationActive()) { // Indicate that we can't obtain a transactional EntityManager. return null; } // Create a new EntityManager for use within the current transaction. // USE CASE 2: @PersistenceContext used within a @Transactional method // linked to another TransactionManager logger.debug("Opening JPA EntityManager"); EntityManager em = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager()); if (TransactionSynchronizationManager.isSynchronizationActive()) { logger.debug("Registering transaction synchronization for JPA EntityManager"); // Use same EntityManager for further JPA actions within the transaction. // Thread object will get removed by synchronization at transaction completion. emHolder = new EntityManagerHolder(em); Object transactionData = prepareTransaction(em, emf); TransactionSynchronizationManager.registerSynchronization( new EntityManagerSynchronization(emHolder, emf, transactionData, true)); emHolder.setSynchronizedWithTransaction(true); // Register the newly created EntityManager in the // TransactionSynchronizationManager map // using the EntityManagerFactory as search key TransactionSynchronizationManager.bindResource(emf, emHolder); } return em; }
The core of the EntityManager injection is done in this class
- First Spring will look into the TransactionSynchronizationManager ThreadLocal map to check if there is a bound EntityManager (lines 8 & 9)
- If a bound EntityManager is found, Spring just joins the existing transaction (lines 20) and returns this EntityManager instance (line 28). This is USE CASE 3 when @PersistenceContext is used within nested @Transactional methods
- If TransactionSynchronizationManager.isSynchronizationActive() is false, it means that the @PersistenceContext is used with NON @Transactional method. This is USE CASE 1. Spring simply retuns null so the SharedEntityManagerCreator will create itself a new EntityManager instance, see line 73 of Point L
- The last case is USE CASE 2 when @PersistenceContext is used within a @Transactional method but linked to another TransactionManager. Spring will:
- Create a new EntityManager instance from scratch (lines 41 & 42)
- Wrap an EntityManagerHolder around it (line 48)
- Register the synchronization with the TransactionSynchronizationManager (lines 51 & 52)
- Bind this EntityManagerHolder to the TransactionSynchronizationManager ThreadLocal map (line 58)
- Return this EntityManager (line 61)
Please note that in this case, the EntityManager created by Spring will not participate in the transaction created by Spring @Transactional(value=”anotherTransactionManager”) because this transaction is registed with the EntityManager created with respect to the declared anotherTransactionManager. Consequently, any DML operation will be simply rolled back at the end of the transaction for this particular EntityManager
IV Summary
- USE CASE 1: @PersistenceContext used with NON @Transactional method
- Create an EntityManager instance directly from the EntityManagerFactory
- USE CASE 2: @PersistenceContext used with @Transactional method declared for another persistenceUnit
- Create an EntityManager instance directly from the EntityManagerFactory and register it with TransactionSynchronizationManager.bindResource(emf, emHolder) for later use
- USE CASE 3: @PersistenceContext used within nested @Transactional methods
- Retrieve an existing EntityManager instance from TransactionSynchronizationManager.getResource(emf) using the EntityManagerFactory as search key
Again, we can see in this code analysis the key role of the TransactionSynchronizationManager managing the lifecycle of EntityManager instances.
Pingback: Hibernate Long/Extended session « Yet Another Java Blog
Pingback: Hibernate Long/Extended session « Yet Another Java Blog
What an excellent analysis. Thank you for this. Very helpful.
You’re welcomed Mark. Nowadays few people really take time to look into source code to understand how things work.
It makes me think about Spring magic tags (like ) or some convention over configuration stuff that do everything to you in the background.
Maybe it deserves a post if I have some time 🙂
Thank you so much. You saved my day.
This is an excellent post
Great work!!!!!!!