【发布时间】:2012-03-14 21:57:38
【问题描述】:
我在配置一个使用 JPA 和 Hibernate 进行单元测试的 Spring 应用程序时遇到问题。 我有 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(全局事务),我只使用本地事务。
生产用的spring配置是:
<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配置:
<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 和 DB 之间有事务,但是在单元测试中我只定义本地事务,所以我在测试应用程序时受到限制。 有了这个限制,我定义了我的单元测试。
现在我遇到了 Hibernate 和 LAZY 加载关系的问题。在单元测试中,EntityManager 会话在找到方法之后关闭,然后延迟加载的代理不起作用。(正如预期的那样,这是在 Hibernate 中的定义) 我的问题是 Bean PersistenceAnnotationBeanPostProcessor 它没有为单元测试设置任何单元名称,并且每当他找到注释 @PersistenceContext 时,他都会插入一个新的 EntityManger,该 EntityManger 是从 spring 配置中定义的 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 类是使用来自 DB 的 Hibernate 工具和实体类生成的。
现在问题出在这行: SyDbVersion 实例 = entityManager.find(SyDbVersion.class, id); 每次我从 find 方法返回时,会话都会关闭,因此惰性成员不再可用。
我正在考虑使用持久单元名称正确配置 PersistenceAnnotationBeanPostProcessor 的方法,但 bean 正在搜索 JNDI 中的持久单元,我找不到为持久单元注册 JNDI 条目的适当时间。
在生产中,因为我设置了持久化 PersistenceAnnotationBeanPostProcessor,因此 EntityManager 被正确共享,并且每次查找后会话都不会关闭。
另一个解决方案是使用 OpenEJB 或嵌入式 glassfish 在单元测试中模拟/拥有一个应用服务器(将成为集成测试)。
我必须在 spring 配置或代码中修改哪些选项以避免在单元测试中出现这个问题?
【问题讨论】:
标签: hibernate spring jpa-2.0 lazy-loading