Настройка приложений Spring JPA с Hibernate для модульного тестирования (отложенная загрузка) - PullRequest
6 голосов
/ 24 февраля 2012

У меня проблема с настройкой одного приложения 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, определенной в весенней конфигурации для тестирования. Теперь в модульном тесте есть элемент @PersistenceContext entityManager и класс 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 с именем персистентного блока, но бин ищет затем блок персистентности в JNDI, и я не могу найти подходящее время для регистрации записи JNDI для блока персистентности.

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

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

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

1 Ответ

1 голос
/ 27 февраля 2012

Я выяснил проблему с моим кодом.Проблема заключалась в том, что когда ApplicationContext был загружен, я кэшировал некоторые объекты JPA в объекте Bean.Позже в тестовом методе (в другой транзакции) я получил доступ к этим ленивым загрузочным отношениям.Конечно, это не работает по проекту, и вы должны получить доступ к отношению, загруженному с отложенной загрузкой, в том же контексте транзакции, в противном случае вы получите исключение, как в моем случае.

использовать вместо отложенной загрузки, загрузки EAGER.

явно инициализировать все необходимые ассоциации и коллекции перед возвратом.Затем вы можете получить к ним доступ и в другой транзакции.

Узел n = // .. получить узел

Hibernate.initialize (n);// инициализирует 'parent' аналогично getParent.

Hibernate.initialize (n.getChildren ());// передать отложенную коллекцию в сеанс для инициализации.

попытаться сохранить сеанс / транзакцию открытой, пока она вам не понадобится.В моём случае это не реально и не имеет смысла.

...