我的项目架构是带有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()”之类的延迟加载语句不再起作用,而我得到了“无法初始化”代理-没有会话”错误。
我知道的一种解决方案是在出现延迟加载问题的服务方法上强制使用@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。
我还尝试将@Transactional添加到“ testHelperActionLoggedIn()”测试方法本身,但这并没有改变结果。
此外,我尝试通过使用@ 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)
似乎在获取动作映射时遇到了麻烦,这是以前没有的。
您可以将@Transactional注解放在测试方法上,并且需要使用spring运行测试才能找到@Transactional注解。要在Struts2测试中使用JUnit4,您需要扩展StrutsSpringJUnit4TestCase。因此您的测试类应如下所示:
@Transactional
StrutsSpringJUnit4TestCase
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:applicationContext.xml"}) public class ActionTest extends StrutsSpringJUnit4TestCase { @Transactional @Test public void testHelperActionLoggedIn() throws Exception { // ... } }
注意:如果需要获取ActionProxy,可以通过调用getActionProxymethod获得。您可能需要为其创建新的会话映射,然后才能致电execute。
ActionProxy
getActionProxy
execute
ActionProxy actionProxy = getActionProxy("/action"); Map<String, Object> sessionMap = new HashMap<String, Object>(); actionProxy.getInvocation().getInvocationContext().setSession(sessionMap); actionProxy.execute();
但是,如果您不需要引用,ActionProxy则可以使用executeAction方法执行操作,而无需创建新的会话映射。
executeAction
executeAction("/action");