Mir*_*ile 6 spring hibernate lazy-loading jpa-2.0
我在配置一个使用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>
Run Code Online (Sandbox Code Playgroud)
用于检测:
<?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>
Run Code Online (Sandbox Code Playgroud)
区别在于单元测试我不使用任何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/>
Run Code Online (Sandbox Code Playgroud)
单元测试的弹簧配置:
<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" />
Run Code Online (Sandbox Code Playgroud)
我花了相同的时间来决定这个配置,应用程序需要全局事务,因为我们在JMS和DB之间有事务,但在单元测试中我只定义了本地事务,所以我在测试应用程序时受到限制.有了这个限制,我定义了我的单元测试.
现在我遇到了Hibernate和LAZY加载关系的问题.在单元测试EntityManager的会议之后关闭找方法,然后和代理的延迟加载是行不通的.(按照定义,它在休眠正如预期)我的问题是豆PersistenceAnnotationBeanPostProcessor它doenst有单元测试,任何单元名集每当他找到注释@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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
SyDbVersionHome类是使用DB和Entity类中的Hibernate Tools生成的.
现在的问题是这一行:SyDbVersion instance = entityManager.find(SyDbVersion.class,id); 每次当我从find方法返回时,会话都会关闭,因此懒惰的成员不再可用.
我在想办法正确配置与坚持单元名称PersistenceAnnotationBeanPostProcessor但豆正在搜索随后在JNDI持久性单元,我无法找到合适的时间来注册持久性单元一个JNDI条目.
在生产中,因为我设置了持久性PersistenceAnnotationBeanPostProcessor,然后正确地共享EntityManager,并且每次查找后都不会关闭会话.
另一种解决方案是使用OpenEJB或embedded-glassfish在单元测试中模拟/拥有应用服务器(将成为集成测试).
我必须在弹簧配置或代码中修改哪些选项以避免单元测试中出现此问题?
我发现我的代码有问题。问题是当加载 ApplicationContext 时,我正在 Bean 对象中缓存一些 JPA 实体。后来在测试方法(在另一个事务中)中,我访问了这种延迟加载关系。当然,这在设计上是行不通的,您必须在同一事务上下文中访问通过延迟加载加载的关系,否则您会得到像我的情况一样的异常。
解决方案:
使用 EAGER 加载代替 LAZY 加载。
在返回之前显式初始化所需的所有关联和集合。然后您也可以在另一笔交易中访问它们。
Node n = // .. 获取节点
Hibernate.initialize(n); // 类似于 getParent 初始化 'parent'。
Hibernate.initialize(n.getChildren()); // 将惰性集合传递到要初始化的会话中。
尝试保持会话/事务打开,直到您需要它为止。就我而言,这实际上是不可能的,也没有任何意义。
| 归档时间: |
|
| 查看次数: |
7321 次 |
| 最近记录: |