Dependency Injection with Spring/Junit/JPA

29,948

These are the correct combinations of annotation + interface:

@PersistenceContext
private EntityManager entityManager;


@PersistenceUnit
private EntityManagerFactory entityManagerFactory;

But when using spring's transaction and entity manager support, you don't need the EntityManagerFactory at all.

The reason why you don't need EntityManagerFactory is because the creation of the EntityManager is responsibility of the transaction manager. Here's what happens in short:

  • the transaction manager is triggered before your methods
  • the transaction manager gets the EntityManagerFactory (it is injected in it), creates a new EntityManager, sets in in a ThreadLocal, and starts a new transaction.
  • then it delegates to the service method
  • whenever @PersistenceContext is encountered, a proxy is injected (in your Dao), which, whenever accessed, gets the current EntityManager which has been set in the ThreadLocal
Share:
29,948
Steve
Author by

Steve

Updated on August 07, 2020

Comments

  • Steve
    Steve almost 4 years

    I'm trying to create JUnit tests for my JPA DAO classes, using Spring 2.5.6 and JUnit 4.8.1.

    My test case looks like this:

    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"classpath:config/jpaDaoTestsConfig.xml"} )
    public class MenuItem_Junit4_JPATest extends BaseJPATestCase {
    
        private ApplicationContext context;
        private InputStream dataInputStream;
        private IDataSet dataSet;
    
        @Resource
        private IMenuItemDao menuItemDao;
    
        @Test
        public void testFindAll() throws Exception {
            assertEquals(272, menuItemDao.findAll().size());
        }
    
        ... Other test methods ommitted for brevity ...
    }
    

    I have the following in my jpaDaoTestsConfig.xml:

    
    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/tx
                               http://www.springframework.org/schema/tx/spring-tx.xsd">
    
      <!-- uses the persistence unit defined in the META-INF/persistence.xml JPA configuration file -->
      <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
            <property name="persistenceUnitName" value="CONOPS_PU" /> 
      </bean>
    
        <bean id="groupDao" class="mil.navy.ndms.conops.common.dao.impl.jpa.GroupDao" lazy-init="true" />
        <bean id="permissionDao" class="mil.navy.ndms.conops.common.dao.impl.jpa.PermissionDao" lazy-init="true" />
        <bean id="applicationUserDao" class="mil.navy.ndms.conops.common.dao.impl.jpa.ApplicationUserDao" lazy-init="true" />
        <bean id="conopsUserDao" class="mil.navy.ndms.conops.common.dao.impl.jpa.ConopsUserDao" lazy-init="true" />
    
        <bean id="menuItemDao" class="mil.navy.ndms.conops.common.dao.impl.jpa.MenuItemDao" lazy-init="true" />
    
     <!-- enables interpretation of the @Required annotation to ensure that dependency injection actually occures -->
      <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>  
    
      <!-- enables interpretation of the @PersistenceUnit/@PersistenceContext annotations providing convenient
           access to EntityManagerFactory/EntityManager -->
      <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
    
      <!-- transaction manager for use with a single JPA EntityManagerFactory for transactional data access
           to a single datasource -->
      <bean id="jpaTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
          <property name="entityManagerFactory" ref="entityManagerFactory"/>
      </bean>
    
      <!-- enables interpretation of the @Transactional annotation for declerative transaction managment
           using the specified JpaTransactionManager -->
      <tx:annotation-driven transaction-manager="jpaTransactionManager" proxy-target-class="false"/>
    
    </beans>
    

    Now, when I try to run this, I get the following:

    SEVERE: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@fa60fa6] to prepare test instance [null(mil.navy.ndms.conops.common.dao.impl.MenuItem_Junit4_JPATest)]
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mil.navy.ndms.conops.common.dao.impl.MenuItem_Junit4_JPATest': Injection of resource fields failed; nested exception is java.lang.IllegalStateException: Specified field type [interface javax.persistence.EntityManagerFactory] is incompatible with resource type [javax.persistence.EntityManager]
        at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessAfterInstantiation(CommonAnnotationBeanPostProcessor.java:292)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:959)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:329)
        at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
        at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
        at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:255)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:93)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:130)
        at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:61)
        at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:54)
        at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
        at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
        at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:52)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
    Caused by: java.lang.IllegalStateException: Specified field type [interface javax.persistence.EntityManagerFactory] is incompatible with resource type [javax.persistence.EntityManager]
        at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.checkResourceType(InjectionMetadata.java:159)
        at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.(PersistenceAnnotationBeanPostProcessor.java:559)
        at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$1.doWith(PersistenceAnnotationBeanPostProcessor.java:359)
        at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:492)
        at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:469)
        at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findPersistenceMetadata(PersistenceAnnotationBeanPostProcessor.java:351)
        at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(PersistenceAnnotationBeanPostProcessor.java:296)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors(AbstractAutowireCapableBeanFactory.java:745)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:448)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
        at java.security.AccessController.doPrivileged(AccessController.java:219)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:221)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:168)
        at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:435)
        at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:409)
        at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:537)
        at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:180)
        at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:105)
        at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessAfterInstantiation(CommonAnnotationBeanPostProcessor.java:289)
        ... 18 more
    
    

    It seems to be telling me that its attempting to store an EntityManager object into an EntityManagerFactory field, but I don't understand how or why. My DAO classes accept both an EntityManager and EntityManagerFactory via the @PersistenceContext attribute, and they work find if I load them up and run them without the @ContextConfiguration attribute (i.e. if I just use the XmlApplcationContext to load the DAO and the EntityManagerFactory directly in setUp ()).

    Any insights would be appreciated.

  • Steve
    Steve about 14 years
    You still need both the EntityManager and Factory in the DAOs, which is the only place they exist in my code.
  • Bozho
    Bozho about 14 years
    no, you don't. For example my entire application has no access to the factory whatsoever. The EntityManager is injected by spring, so no need to use the factory manually.
  • Steve
    Steve about 14 years
    Right. We're doing the same thing with the DAOs (injecting the EntityManager). I'm also injecting the Factory (although I'm not currently using it for anything). It turns out that the whole problem was a simple cut-and-paste error. I was using @PersistenceContext on the EntityManagerFactory, rather than @PersistenceUnit. Once I changed that it worked... Thanks for the pointer...
  • Bozho
    Bozho about 14 years
    @Steve - accepted answer with 0 upvotes seems strange, so I'd suggest your practice being the regular - accept + upvote
  • emeraldjava
    emeraldjava over 13 years
    IMO this answer is not complete, since a clear spring config should be provided for @PersistenceContext private EntityManager entityManager; example. Why group the EntityManager and EntityManagerFactory in the same block when they should never appear in the same class together.