Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure Spring JPA applications with Hibernate for unit testing(lazy-loading)

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.JtaTransactionManager">
   <property name="entityManagerFactory" ref="entityManagerFactory"/>
 </bean>

 <bean id="persAnnoBeanPostProc" class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" >
   <property name="persistenceUnits">
   <map>
       <entry key="prod_pu" 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.LocalContainerEntityManagerFactoryBean">
    <!-- 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.PersistenceAnnotationBeanPostProcessor" >
  </bean>

  <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <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.SyDbVersion
 * @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?

like image 738
Mirea Vasile Avatar asked Feb 24 '12 12:02

Mirea Vasile


People also ask

How lazy loading works in Hibernate?

Lazy loading in Hibernate means fetching and loading the data, only when it is needed, from a persistent storage like a database. Lazy loading improves the performance of data fetching and significantly reduces the memory footprint.

How lazy loading works in JPA?

By default, JPA uses the lazy fetch strategy in associations of type @ElementCollection. Thus, any access to the collection in a closed Persistence Context will result in an exception. This test throws an exception when we try to access the phone list because the Persistence Context is closed.

What is lazy loading in Spring JPA?

With a LAZY type dependency, only data of the wanted object is loaded: author's data is not retrieved. With Spring Data JPA, every relationship between 2 domain objects owns one of these data loading types. By default, the method will be determined by the relationship type.

What does the configuration property Spring JPA properties Hibernate Enable_lazy_load_no_trans true do?

More concretely, it provides powerful tooling to generate Spring Data JPA repositories and methods, Flyway Versioned Migrations, Liquibase Differential Changelogs, DDL and SQL statements, DTO objects, and MapStruct interfaces.


1 Answers

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.

Solution:

  1. use instead of LAZY loading, EAGER loading.

  2. explicitly initialize all associations and collections required before returning it. Then you can access them in another transaction too.

    Node n = // .. get the node

    Hibernate.initialize(n); // initializes 'parent' similar to getParent.

    Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session to be initialized.

  3. try to keep the session/transaction open until you need it. In my case is not really possible and make no sense.

like image 120
Mirea Vasile Avatar answered Oct 30 '22 13:10

Mirea Vasile