2012-02-24 12 views
6

ユニットテストのためにJPAとHibernateを使用する1つのSpringアプリケーションを設定する際に問題があります。 私は2つのpersistence.xmlファイルをプロダクション用とユニットテスト用の2つ持っています。テスト用ユニットテスト(遅延ロード)のためにHibernateでSpring JPAアプリケーションを設定する

<?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とDBの間のトランザクションを持っていますが、単体テストではローカルトランザクションのみを定義するので、アプリケーションのテストには限界があります。 この制限で私は単体テストを定義します。

今、私は関係のHibernateとLAZYの読み込みに問題があります。ユニットテストでは、メソッドの検索後にEntityManagerセッションが終了し、LAZYローディングのプロキシが機能していません(これは、Hibernateの定義どおりです) 私の問題は、ユニットテスト用に設定されたユニット名を持つBean PersistenceAnnotationBeanPostProcessorです@PersistenceContextというアノテーションを見つけるたびに、彼はテストのためにSpringコンフィグレーションで定義されたEntityManagerFactoryから作成された新しいEntityMangerを挿入しています。 は今ユニットテストは、あまりにも@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とEntityクラスからHibernateツールで生成されます。

問題は次の行です。 SyDbVersion instance = entityManager.find(SyDbVersion.class、id); findメソッドから戻ってくるたびに、セッションは閉じられ、怠け者はもう利用できなくなります。

永続ユニット名でPersistenceAnnotationBeanPostProcessorを正しく設定する方法を考えていましたが、BeanはJNDIで永続ユニットを検索していて、永続ユニットのJNDIエントリを登録する適切な時間を見つけることができません。

私はpersistent PersistenceAnnotationBeanPostProcessorを設定しているため、EntityManagerは適切に共有されているため、セッションはfind後に毎回閉じられません。

別の解決方法は、OpenEJBまたは埋め込みglassfishを使用して単体テストでアプリケーションサーバーをシミュレート/統合することです(統合テストとなります)。

単体テストでこの問題を回避するには、スプリング構成またはコードでどのようなオプションを変更する必要がありますか?

答えて

1

私のコードの問題を発見しました。問題は、ApplicationContextがロードされた後、Beanオブジェクトの一部のJPAエンティティをキャッシュしていたときです。テストメソッドの後半(別のトランザクションで)、私はこの怠惰なローディング関係にアクセスしていました。もちろん、これは設計上の問題ではなく、同じトランザクションコンテキストで遅延読み込みのロードされたリレーションにアクセスする必要があります。そうでないと、私の場合のように例外が発生します。

ソリューション:

  1. 使用の代わりに、遅延読み込み、EAGERロード。

  2. 返す前に必要なすべての関連付けとコレクションを明示的に初期化します。その後、別のトランザクションでもアクセスできます。

    ノードN = // ..ノード

    のHibernate.initialize(n)を得ます。 // getParentと同様に 'parent'を初期化します。

    Hibernate.initialize(n.getChildren()); //レイジーコレクションを初期化するセッションに渡します。

  3. 必要になるまでセッション/トランザクションを開いたままにしてください。私の場合は本当に可能ではなく、意味をなさない。

関連する問題