복붙노트

[SPRING] Struts2; StrutsSpringTestCase JUnit 테스트를 위해 세션 열기

SPRING

Struts2; StrutsSpringTestCase JUnit 테스트를 위해 세션 열기

내 프로젝트 아키텍처는 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;
}

그러나이 방법은 정상적인 상황에서 트랜잭션이 필요하지 않으므로 과도 할 수 있습니다. 서비스 측면에서 타협 할 필요가없는 또 다른 솔루션이 있는지 궁금합니다.

세션을 열어두기 위해 테스트 클래스 (Struts Spring Test Case를 확장)에 추가 할 수있는 것이 있습니까? 또는 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로 주석을 달아 봄에 특화된 (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 Failure Trace에 예외가 발생했습니다. 어떤 이유로 든 예외 출력이 콘솔에 없었습니다. 예외 세부 정보 :

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)

이전에는 없었던 액션 매핑을 얻는 데 문제가있는 것처럼 보입니다.

해결법

  1. ==============================

    1.테스트 메서드에 @Transactional 주석을 붙일 수 있으며 @Transactional 주석을 찾을 수 있도록 봄으로 테스트를 실행해야합니다. Struts2 테스트에서 JUnit4를 사용하려면 StrutsSpringJUnit4TestCase를 확장해야합니다. 따라서 테스트 클래스는 다음과 같이 보일 것입니다.

    테스트 메서드에 @Transactional 주석을 붙일 수 있으며 @Transactional 주석을 찾을 수 있도록 봄으로 테스트를 실행해야합니다. Struts2 테스트에서 JUnit4를 사용하려면 StrutsSpringJUnit4TestCase를 확장해야합니다. 따라서 테스트 클래스는 다음과 같이 보일 것입니다.

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"classpath:applicationContext.xml"})
    public class ActionTest extends StrutsSpringJUnit4TestCase {
      @Transactional
      @Test
      public void testHelperActionLoggedIn() throws Exception {
        // ...
      }
    }
    

    참고 : 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("/action");
    
  2. from https://stackoverflow.com/questions/19056281/struts2-keeping-a-session-open-for-strutsspringtestcase-junit-tests by cc-by-sa and MIT license