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

I had a problem setting up a single Spring application using JPA with Hibernate for unit testing. I have 2 persistence.xml files 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 do not use JTA (global transactions), I use only local transactions.

Spring configuration for production:

  <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/> 

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 the same time to solve me for this configuration, applications need global transactions because we have transactions between the JMS and the database, but in the unit test I only define local transactions, so I am limited in testing the application. With these limitations, I define my unit tests.

Now I have a problem with Hibernate and LAZY loading relationships. In the unit test, the EntityManager session closes after the search methods, and then the proxy for LAZY loading does not work (this is by definition in Hibernate the same as expected) My problem is that the Bean PersistenceAnnotationBeanPostProcessor has any unit name set for unit tests, and anytime he dials the @PersistenceContext annotation, he inserts a new EntityManger created from the EntityManagerFactory defined in the Spring configuration for testing. Unit test now has @PersistenceContext entityManager member and DAO class:

 @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 SyDbVersionHome class is generated using Hibernate Tools from the database and Entity class.

Now the problem is the line: Instance SyDbVersion = entityManager.find (SyDbVersion.class, id); Every time I return from the search method, the session is closed, so lazy members are no longer available.

I was thinking about how to properly set up PersistenceAnnotationBeanPostProcessor with the name of the stored object, but the Bean does a search and then a save block in JNDI, and I cannot find a suitable time to register the JNDI record for the save unit.

During the creation process, since I set PersistenceAnnotationBeanPostProcessor, the EntityManager is then properly partitioned and the session does not close every time after the search.

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

What parameters do I need to change in the Spring configuration or in the code to avoid this problem when testing modules?

+6
source share
1 answer

I will find out the problem with my code. The problem was when the ApplicationContext was loaded, then I was caching some JPA objects in a Bean. Later in the testing method (in another transaction) I got access to these lazy load relationships. Of course, this does not work by design, and you must access relations loaded with lazy loading in the same transaction context, otherwise you will get an exception, as in my case.

Decision:

  • instead of loading LAZY, loading EAGER.

  • explicitly initialize all associations and collections necessary before returning it. Then you can access them in another transaction.

    Node n = // .. get node

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

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

  • try to keep the session / transaction open until you need it. In my case, this is not real and does not make sense.

+1
source

Source: https://habr.com/ru/post/909273/


All Articles