Results 1 to 8 of 8

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

Hybrid View

  1. #1
    Join Date
    Oct 2011
    Posts
    5

    Default 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. #2
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,632

    Default

    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.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  3. #3
    Join Date
    Oct 2011
    Posts
    5

    Default

    Quote 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.

  4. #4
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,632

    Default

    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.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  5. #5
    Join Date
    Oct 2011
    Posts
    5

    Default

    Quote 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.

  6. #6
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,632

    Default

    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).
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •