【问题标题】:Configure Spring JPA applications with Hibernate for unit testing(lazy-loading)使用 Hibernate 配置 Spring JPA 应用程序以进行单元测试(延迟加载)
【发布时间】: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


    【解决方案1】:

    我发现了我的代码的问题。问题是在加载 ApplicationContext 时,我在 Bean 对象中缓存了一些 JPA 实体。后来在测试方法中(在另一个事务中)我正在访问这个延迟加载关系。当然,这不是按设计工作的,您必须在同一个事务上下文中访问通过延迟加载加载的关系,否则您会遇到像我这样的异常。

    解决方案:

    1. 使用而不是 LAZY 加载,EAGER 加载。

    2. 在返回之前显式初始化所有需要的关联和集合。然后你也可以在另一个事务中访问它们。

      Node n = // .. 获取节点

      Hibernate.initialize(n); // 初始化 'parent' 类似于 getParent。

      Hibernate.initialize(n.getChildren()); // 将惰性集合传递给要初始化的会话。

    3. 尝试保持会话/事务打开直到您需要它。在我的情况下是不可能的,没有任何意义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 2012-01-20
      • 2011-06-28
      • 2011-03-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多