У меня проблема с настройкой одного приложения 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 для имитации / использования сервера приложений в модульных тестах (которые затем станут интеграционными тестами).
Какие опции мне нужно изменить в конфигурации пружины или в коде, чтобы избежать этой проблемы при модульном тестировании?