Настройте приложения Spring JPA с Hibernate для модульного тестирования (отложенная загрузка)

У меня проблема с настройкой одного приложения Spring, которое использует JPA с Hibernate для модульного тестирования. У меня есть 2 файла persistence.xml, один для производства и один для модульных тестов. Для тестирования:

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

для тестирования:

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

Разница в том, что в модульных тестах я не использую никаких JTA (глобальных транзакций), я использую только локальные транзакции.

Пружинная конфигурация для производства:

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

Пружинная конфигурация для модульных тестов:

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

Мне потребовалось столько же времени, чтобы выбрать эту конфигурацию, приложениям нужны глобальные транзакции, потому что у нас есть транзакции между JMS и БД, но в в модульном тесте я определяю только локальные транзакции, поэтому я ограничен в тестировании приложения. Этими пределами я определяю свои модульные тесты.

Теперь у меня проблема с Hibernate и LAZY загрузкой отношений. В модульном тесте сеанс EntityManager закрывается после методов поиска, а затем прокси для LAZY загрузки не работает. (Это по определению в Hibernate, как и ожидалось) Моя проблема заключается в том, что Bean PersistenceAnnotationBeanPostProcessor имеет любое имя модуля, установленное для модульных тестов, и каждый раз, когда он находит аннотацию @PersistenceContext, он вставляет новый EntityManger, созданный из EntityManagerFactory, определенного в конфигурации spring для тестирования.Теперь в модульном тесте есть член entityManager @PersistenceContext и класс DAO:

@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;
      }
    }
  }  

Класс SyDbVersionHome генерируется с помощью Hibernate Tools из базы данных и класса Entity.

Теперь проблема в строке: SyDbVersion instance = entityManager.find (SyDbVersion.class, id); Каждый раз, когда я возвращаюсь из метода find, сеанс закрывается, поэтому ленивые участники больше не доступны.

Я думал, как правильно настроить PersistenceAnnotationBeanPostProcessor с именем сохраняемого модуля, но bean-компонент ищет затем модуль сохранения состояния в JNDI, и я не могу найти подходящее время для регистрации записи JNDI для блока сохранения состояния.

В процессе производства, поскольку я установил постоянный PersistenceAnnotationBeanPostProcessor, EntityManager затем правильно разделяется, и сеанс не закрывается каждый раз после поиска.

Другим решением будет использование OpenEJB или embedded-glassfish для моделирования / включения сервера приложений в модульных тестах (позже они станут интеграционными тестами).

Какие параметры мне нужно изменить в конфигурации пружины или в коде, чтобы избежать этой проблемы при модульном тестировании?

6
задан Mirea Vasile 24 February 2012 в 16:46
поделиться