Announcement Announcement Module
Collapse
No announcement yet.
Spring application using JPA with Hibernate, lazy-loading issue in unit test Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring application using JPA with Hibernate, lazy-loading issue in unit test

    I have a problem in configuring one Spring application who is using JPA with Hibernate for unit testing.
    I am having 2 persistence.xml files one for production and one for unit tests.
    For testing:

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <persistence-unit name="prod_pu" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence </provider>
    <jta-data-source>jdbc/ds_prod</jta-data-source>

    <properties>
    <property name="hibernate.bytecode.use_reflection_optimizer" value="false"/>
    <property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver"/>
    <property name="hibernate.connection.password" value="passsample"/>
    <property name="hibernate.connection.url" value="jdbc:oracle:thin:urlsample"/>
    <property name="hibernate.connection.username" value="usernamesample"/>
    <property name="hibernate.default_schema" value="schemassample"/>
    <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
    </properties>
    </persistence-unit>

    </persistence>

    for testing:

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">

    <persistence-unit name="test_pu" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence </provider>
    <properties>
    <property name="hibernate.bytecode.use_reflection_optimizer" value="false"/>
    <property name="hibernate.connection.driver_class" value="oracle.jdbc.OracleDriver"/>
    <property name="hibernate.connection.password" value="passsample"/>
    <property name="hibernate.connection.url" value="jdbc:oracle:thin:urlsample"/>
    <property name="hibernate.connection.username" value="usernamesample"/>
    <property name="hibernate.default_schema" value="schemasample"/>
    <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
    </properties>
    </persistence-unit>

    </persistence>

    The difference is in unit tests I dont use any JTA (global transactions), I use only local transactions.

    The spring configuration for production is:

    <jee:jndi-lookup id="entityManagerFactory" jndi-name="persistence/ds_prod"/>

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTran sactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <bean id="persAnnoBeanPostProc" class="org.springframework.orm.jpa.support.Persist enceAnnotationBeanPostProcessor" >
    <property name="persistenceUnits">
    <map>
    <entry key="fidsdb" value="persistence/prod_pu"/>
    </map>
    </property>
    </bean>

    <context:annotation-config/>
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
    <context:component-scan base-package="com.sample.packagename" />
    <tx:jta-transaction-manager/>


    The spring configuration for unit tests:

    <bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerE ntityManagerFactoryBean">
    <!-- This workaround is necessary because Spring is buggy
    Instead of including the test-classes/META-INF the spring always search into classes/META-INF and ignores the one from test-classes
    -->
    <property name="persistenceXmlLocation" value="META-INF/persistence-test.xml" />
    <property name="persistenceUnitName" value="test_pu" />
    </bean>

    <bean id="persAnnoBeanPostProc" class="org.springframework.orm.jpa.support.Persist enceAnnotationBeanPostProcessor" >
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionM anager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
    <property name="persistenceUnitName" value="test_pu" />
    </bean>

    <context:annotation-config />
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
    <context:component-scan base-package="com.sample.packagename" />


    It took me same time to decide me for this configuration, the applications needs global transactions because we have transactions between JMS and DB but in the unit test I define only local transactions so I am limited in testing the application.
    With this limits I define my unit tests.

    Now I have a problem with Hibernate and LAZY loading of relations. In the Unit test the EntityManager Session is closing after find methods and then and proxy for LAZY loading is not working.(this is by definition in Hibernate just as expected)
    My problem is the Bean PersistenceAnnotationBeanPostProcessor it doenst have any unitname set for unit tests and any time he is finding the annotation @PersistenceContext he is inserting a new EntityManger created from EntityManagerFactory defined in the spring configuration for testing.
    Now the Unit test is having a @PersistenceContext entityManager member and the DAO class too :


    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"classpath:testConfiguration.xml"})
    @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
    @Transactional
    public class ConnectionTest {

    @PersistenceContext
    EntityManager entityManager;

    Logger log = Logger.getLogger(ConnectionTest.class);

    @Resource(name = "syDbVersionDao")
    SyDbVersionDao dbVersionDao;

    @Test
    public void testChanging() {
    String oldVer = dbVersionDao.getCurrentVersion();
    assertNotNull(oldVer);
    }
    }


    @Component
    public class SyDbVersionDao extends SyDbVersionHome {

    @PersistenceContext
    private EntityManager entityManager;

    public String getCurrentVersion() {
    SyDbVersion res = getLastRecord();

    if (res == null) return "";
    return res.getVersion();
    }

    public SyDbVersion getLastRecord(){
    Query query = entityManager.createQuery("from SyDbVersion v order by v.installationDate desc");
    query.setMaxResults(1);
    return (SyDbVersion) query.getSingleResult();
    }
    }


    /**
    * Home object for domain model class SyDbVersion.
    * @see com.tsystems.ac.fids.web.persistence.jpa.SyDbVersi on
    * @author Hibernate Tools, generated!
    */
    @Stateless
    public class SyDbVersionHome {

    private static final Log log = LogFactory.getLog(SyDbVersionHome.class);

    @PersistenceContext private EntityManager entityManager;

    public void persist(SyDbVersion transientInstance) {
    log.debug("persisting SyDbVersion instance");
    try {
    entityManager.persist(transientInstance);
    log.debug("persist successful");
    }
    catch (RuntimeException re) {
    log.error("persist failed", re);
    throw re;
    }
    }

    public void remove(SyDbVersion persistentInstance) {
    log.debug("removing SyDbVersion instance");
    try {
    entityManager.remove(persistentInstance);
    log.debug("remove successful");
    }
    catch (RuntimeException re) {
    log.error("remove failed", re);
    throw re;
    }
    }

    public SyDbVersion merge(SyDbVersion detachedInstance) {
    log.debug("merging SyDbVersion instance");
    try {
    SyDbVersion result = entityManager.merge(detachedInstance);
    log.debug("merge successful");
    return result;
    }
    catch (RuntimeException re) {
    log.error("merge failed", re);
    throw re;
    }
    }

    public SyDbVersion findById( long id) {
    log.debug("getting SyDbVersion instance with id: " + id);
    try {
    SyDbVersion instance = entityManager.find(SyDbVersion.class, id);
    log.debug("get successful");
    return instance;
    }
    catch (RuntimeException re) {
    log.error("get failed", re);
    throw re;
    }
    }
    }


    The class SyDbVersionHome is generated with Hibernate Tools from the DB and the Entity class too.

    Now the problem is the line:
    SyDbVersion instance = entityManager.find(SyDbVersion.class, id);
    Every time when I come back from the find method the session is closed so the lazy members are not available any more.

    I was thinking a way to configure properly the PersistenceAnnotationBeanPostProcessor with the persist unit name but the bean is searching then the persistence unit in JNDI and I cannot find the proper time to register a JNDI entry for the persistence unit.

    In production because I set the persist PersistenceAnnotationBeanPostProcessor the EntityManager is then properly shared and the session is not closed every time after find.

    Another solution will be to use OpenEJB or embedded-glassfish to simulate/have an application server in the unit tests ( will became then integration-tests).

    What options I have to modify in the spring configuration or in the code to avoid this problem in unit testing?

  • #2
    Please use [ code][/code ] tags when posting code, that way we can read your source code... !

    If you get the problems as described you have either a transaction setup problem OR your methods operate on a new entitymanager (due to REQUIRES_NEW propagation level).

    Also your code is strange, I suggest NOT to use the generation stuff from hibernate. You are mixing EJB and Spring annotations (@Stateless vs @Component), I suggest removing the @Stateless (as that also might screw up things on the server!) also your dao should be @Repository annotated and not @Component (to correctly stereotype it).

    Your hibernate.connection properties are useless because you are injecting the datasource yourself.

    You have @Transactional enabled but your class isn't annotated with @Transactional, so basically you don't have transactions.

    Comment


    • #3
      Originally posted by Marten Deinum View Post
      Please use [ code][/code ] tags when posting code, that way we can read your source code... !

      If you get the problems as described you have either a transaction setup problem OR your methods operate on a new entitymanager (due to REQUIRES_NEW propagation level).

      Also your code is strange, I suggest NOT to use the generation stuff from hibernate. You are mixing EJB and Spring annotations (@Stateless vs @Component), I suggest removing the @Stateless (as that also might screw up things on the server!) also your dao should be @Repository annotated and not @Component (to correctly stereotype it).

      Your hibernate.connection properties are useless because you are injecting the datasource yourself.

      You have @Transactional enabled but your class isn't annotated with @Transactional, so basically you don't have transactions.
      Thanks for the answer.

      1.I know about @Stateless it is planned to take it out, but for the moment is not creating any issues in production on the glassfish server.

      2.About @Repository annotated and not @Component I think is a good ide but I saw the comment that using it for DAO you must be very carefull so I need to see what are the constraints.

      3. About hibernate.connection properties are useless in the production, only there I use a data source, in the unit tests I dont have a data source. From the production I can tace them out.

      4.About @Transactional I don't understand you. I use:

      @Transactional
      public class ConnectionTest {
      }

      This means by default is set REQUIRES_NEW and the transaction should start in all methods from the unit tests.
      Why this should not work ?
      This could be the reason of the issue if you can explain me here what is wrong or what should I fix.

      Comment


      • #4
        The default is REQUIRED if you get new sessions for find method those run in REQUIRES_NEW. As I stated you don't have transactions in your other layers as there is no @Transactional annotation.

        Your test and production code should be the same as much as possible, if you inject a datasource in production do so in your tests!

        Regarding @Component and @Repository if you want to use consistent transaction management you need exception translation active and that is active with @Repository.

        Comment


        • #5
          Originally posted by Marten Deinum View Post
          The default is REQUIRED if you get new sessions for find method those run in REQUIRES_NEW. As I stated you don't have transactions in your other layers as there is no @Transactional annotation.

          Your test and production code should be the same as much as possible, if you inject a datasource in production do so in your tests!

          Regarding @Component and @Repository if you want to use consistent transaction management you need exception translation active and that is active with @Repository.

          Ok, but my problem is now to detect what method is requiring a new transaction (REQUIRES_NEW) ?
          I dont think find is such a method.
          In my code I start a transaction in the test class with default REQUIRED. This means I use/start a transaction for sure.
          Then I don't expect any method/class with the transactional REQUIRED_NEW.
          In my classes I don't use transactional only in the test class this means the reason for a new transaction it cannot be located in my classes.
          This is actually my problem, I was thinking maybe I get a hint what method from EntityManager use/need a new transaction and maybe why.

          Comment


          • #6
            I strongly suggest you read the reference guide about transactions and resource management.

            For starters switch to @Repository and put an @Transactional on the dao as that makes sure the dao executes in the same transaction as the test case.

            Also I suggest changing your setup using different configs and classes for your test as in production makes your test basically useless you aren't testing the real classes. Also your setup is strange due to the crappy generated stuff (that should only be used for EJB and next to that you should do that yourself). Your class has 2 EntityManagers and that is probably the root cause of your problem (which stems from the generated stuff from hibernate).

            Comment


            • #7
              Originally posted by Marten Deinum View Post
              I strongly suggest you read the reference guide about transactions and resource management.

              For starters switch to @Repository and put an @Transactional on the dao as that makes sure the dao executes in the same transaction as the test case.

              Also I suggest changing your setup using different configs and classes for your test as in production makes your test basically useless you aren't testing the real classes. Also your setup is strange due to the crappy generated stuff (that should only be used for EJB and next to that you should do that yourself). Your class has 2 EntityManagers and that is probably the root cause of your problem (which stems from the generated stuff from hibernate).

              I find out the problem of my code. The problem was when the ApplicationContext was loaded then I was Caching some JPA entities in a Bean Object. Later in the test method (in another transaction) I was accesing this lazy loading relations. Of course this is not working by design and you must access the relation loaded with lazy loading in the same transaction context otherwise you get an exception like in my case.

              This means actually the test case is working perfect, I have there only one transaction as expected.

              I know the code is actually for a prototype, and at the first codereview is not looking great
              But I will improve some parts (like replacing @Stateless with @Repository). Because of time reasons I am using Hibernate Tools and I am happy for that decision. This doenst mean that I need to use them later too.

              About the multiple EntityManager is allowed to have something like this and thats the mechanism of using a "Inject a shared transactional EntityManager proxy", see PersistenceAnnotationBeanPostProcessor and his method resolveEntityManager.

              I will put a transactional on the Dao level too just to be sure(I have have a @Transactional on the Service level anyway).
              Thanks for your sugestions.

              Comment


              • #8
                Not sure what time you gain, I suggest take a look at Spring Data JPA that would save time as you don't have to write code anymore. Next to that I think having 2 instances (well actually 1 but references twice in your dao) isn't clear coding I would make the one in the super class protected (or default if the dao is in the same package).

                Finally @Transactional should actually be only on the service layer, I was mentioning this to see if it would help your testcase (which it obviously wouldn't if you where caching objects). I suggest if you want to use caching take a look at the 2nd level caching options for hibernate and spring instead of inventing your own.

                Comment

                Working...
                X