私のプロジェクト アーキテクチャは、Spring 統合と JPA/Hibernate を備えた Struts2 です。StrutsSpringTestCase 基底クラスは、JUnit 統合テストに使用されます。
通常の状況では、web.xml の次の構成により、各リクエストの開始から終了まで単一のセッションを開いたままにします。
<filter>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
その結果、すべての遅延読み込みがすべてのサービスで正常に機能します。例えば:
@Override
public Person find(int id) {
Person person = personDao.find(id);
// Take care of lazy loading before detaching the object for
// the view layer...
person.getGender().getCode();
// Detach the object so that it can be used for data transfer
// (as a DTO) without causing JPA issues and errors...
getEntityManager().detach(person);
return person;
}
さて... web.xml の OpenEntityManagerInViewFilter 構成とは無関係の統合テストを実行しようとすると、問題が発生します。何が起こるかというと、各リクエストの最初から最後まで開いたままになっているセッションがないため、「person.getGender().getCode()」のような遅延ロード ステートメントが機能しなくなり、「could not initialize」というメッセージが表示されます。プロキシ - セッションなし」エラー。
私が認識している 1 つの解決策は、遅延読み込みの問題があるサービス メソッドに @Transactional アノテーションを強制することです。これにより、メソッド呼び出しの開始から終了までセッションが開かれます。私はそれをテストし、問題を修正しました:
@Transactional
@Override
public Person find(int id) {
Person person = personDao.find(id);
// Take care of lazy loading before detaching the object for
// the view layer...
person.getGender().getCode();
// Detach the object so that it can be used for data transfer
// (as a DTO) without causing JPA issues and errors...
getEntityManager().detach(person);
return person;
}
ただし、通常の状況ではメソッドはトランザクションを必要としないため、これはやり過ぎかもしれません。サービス側で妥協する必要のない別の解決策があるかどうか疑問に思っています。
セッションを開いたままにするために、テスト クラス (StrutsSpringTestCase を拡張する) に追加できるものはありますか? それとも、Spring または JUnit 側に洗練された構成ソリューションがあるのでしょうか?
これが私のSpring構成ファイルです-applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
default-dependency-check="all"
default-lazy-init="false"
default-autowire="byName">
<!-- *************** MAIN CONFIGURATION SECTION *************** -->
<!-- Bean post-processor for JPA annotations. -->
<!-- Make the Spring container act as a JPA container and inject an EnitityManager from
the EntityManagerFactory. -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"
autowire="no"
dependency-check="none" />
<!-- ** Data Source Configuration ** -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
autowire="no"
dependency-check="none">
<!-- Database configuration: -->
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost/**********" />
<property name="user" value="**********" />
<property name="password" value="**********" />
<!-- C3P0 pooling properties configuration: -->
<property name="acquireIncrement" value="4" />
<property name="initialPoolSize" value="4" />
<property name="minPoolSize" value="4" />
<property name="maxPoolSize" value="20" />
<property name="maxIdleTime" value="600" />
<property name="maxConnectionAge" value="1800" />
</bean>
<!-- ** JPA Vendor Selection ** -->
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
autowire="no"
dependency-check="none" />
<!-- ** JPA Vendor and Entity Manager Configuration ** -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
autowire="no"
dependency-check="none">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<!-- Have the JPA vendor manage the database schema: -->
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="hibernate.max_fetch_depth">4</prop>
<prop key="hibernate.jdbc.batch_size">1000</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">false</prop>
</props>
</property>
</bean>
<!-- ** Transaction Manager Configuration ** -->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
autowire="no"
dependency-check="none">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- ** Transaction Annotation Configuration; classes/functions with @Transactional will
get a framework transaction. ** -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- **** DETAILED SERVICE BEAN CONFIGURATION WAS TAKEN OUT TO SHORTEN THE FILE **** -->
</beans>
ポインタをいただければ幸いです。
編集:
物事をもう少し視覚的にするために、次のテストでは、問題のサービス メソッドが遅延読み込みに遭遇し、サービス メソッドに @Transactional のアノテーションが付けられていない場合に例外を生成しますが、サービス メソッドに @Transactional のアノテーションが付けられている場合は問題なく動作します。
public class ActionTest extends CustomActionTestBase {
public ActionTest() {
super("/web/someAction"); // the action to test
}
@Override
public void testHelperActionLoggedIn() throws Exception {
procApplyContinualSessionForAdmin(); // the numerous steps to get logged in
procExecuteAction(
helpGetPrimaryActionURI(), // use the action URI set by the constructor above
helpPrepareActionParams( ) // no parameters are passed to this action
);
procConfirmOutcome(ActionSupport.SUCCESS,0,0,0,false);
}
}
注: CustomActionTestBase は StrutsSpringTestCase を拡張します (これにより、一部の JUnit が拡張されます)。重いテスト ケースのカスタマイズ/自動化のために、CustomActionTestBase が必要でした。
編集:
また、「testHelperActionLoggedIn()」テスト メソッド自体に @Transactional を追加しようとしましたが、結果は変わりませんでした。
編集:
さらに、@RunWith、@ContextConfiguration、および @Test で注釈を付けることで、物事をより Spring 固有のものにしようとしました (Aleksandr M の指示に従って)。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"})
public class ActionTest extends CustomActionTestBase {
public ActionTest() {
super("/web/someAction"); // the action to test
}
@Test
@Override
public void testHelperActionLoggedIn() throws Exception {
procApplyContinualSessionForAdmin(); // the numerous steps to get logged in
procExecuteAction(
helpGetPrimaryActionURI(), // use the action URI set by the constructor above
helpPrepareActionParams( ) // no parameters are passed to this action
);
procConfirmOutcome(ActionSupport.SUCCESS,0,0,0,false);
}
}
その結果、JUnit 障害トレースに例外が表示されました。何らかの理由でコンソールに例外が出力されませんでした。例外の詳細:
java.lang.NullPointerException
at org.apache.struts2.StrutsTestCase.getActionMapping(StrutsTestCase.java:196)
at org.apache.struts2.StrutsTestCase.getActionMapping(StrutsTestCase.java:206)
at com.mycompany.utils.test.CustomActionTestBase.examineActionMapping(CustomActionTestBase.java:402)
at com.mycompany.utils.test.CustomActionTestBase.procExecuteAction(CustomActionTestBase.java:158)
at com.mycompany.utils.test.CustomActionTestBase.execLoginActionForAdmin(CustomActionTestBase.java:505)
at com.mycompany.utils.test.CustomActionTestBase.procApplyContinualSessionForAdmin(CustomActionTestBase.java:106)
at com.mycompany.actions.web.ActionTest.testHelperActionLoggedIn(ActionTest.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
以前はなかったアクション マッピングの取得に問題があるようです。