2014-02-13 3 views
1

Spring @ Transactional 어노테이션을 사용하는 시스템에 대한 베어 니스 미니멀리스트 JUnit 테스트를 작성하려고하는데 많은 성공을 거두지 못했습니다.Spring @ Transactional

고유 제한 조건이있는 열에 대해 동일한 값을 사용하여 두 개의 인스턴스를 만듭니다. 두 인스턴스 생성이 다른 트랜잭션에서 발생하면 첫 번째 트랜잭션이 커밋되고 두 번째 트랜잭션이 예외를 throw하여 한 행으로 이어질 것으로 예상합니다. 동일한 트랜잭션에서 두 개의 삽입이 발생하면 둘 다 원자 단위로 롤백 될 것으로 예상됩니다. 어딘가에 구성 문제가 있다고 확신하지만, 운이 좋지는 않습니다.

테스트에는 하나 또는 두 개의 인스턴스를 만드는 메소드가있는 Bean (ContextTestHelperImpl/ContextTestHelper)이 있습니다.

@Override 
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, 
     rollbackFor = {ContextDuplicationException.class}) 
public Foo createOneFoo(int val) throws ContextDuplicationException { 
    try { 
     return fooDAO.createFoo(val); 
    } catch (Throwable th) { 
     throw new ContextDuplicationException(th); 
    } 
} 

@Override 
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, 
     rollbackFor = {ContextDuplicationException.class}) 
public Foo[] createTwoFoos(int val) throws ContextDuplicationException { 
    try { 
     return new Foo[] { fooDAO.createFoo(val), fooDAO.createFoo(val) }; 
    } catch (Throwable th) { 
     throw new ContextDuplicationException(th); 
    } 
} 

내가 하나 개 JUnit 테스트 (하여 ContextTest)가 두 번 첫 번째 방법을 호출 : 예상대로이 작동

public void dupTestTwoTransactions() { 
    contextTestHelper.deleteAllFoo(); 
    Assert.assertEquals(0, contextTestHelper.getCountOfFoo()); 

    boolean hadException = false; 
    try { 
     contextTestHelper.createOneFoo(DUPLICATE_VALUE); 
    } catch (ContextDuplicationException th) { 
     hadException = true; 
     System.out.println("[ContextTest][dupTestTwoTransactions] UNEXPECTED ERROR: " + th); 
     th.printStackTrace(); 
    } 
    Assert.assertEquals(hadException, false); 
    Assert.assertEquals(1, contextTestHelper.getCountOfFoo()); 

    try { 
     contextTestHelper.createOneFoo(DUPLICATE_VALUE); 
    } catch (ContextDuplicationException th) { 
     hadException = true; 
    } 
    Assert.assertEquals(hadException, true); 
    Assert.assertEquals(1, contextTestHelper.getCountOfFoo()); 
} 

각각의 방법에 Propagation.REQUIRES_NEW 주석이 있습니다. 첫 번째 호출은 예외를 throw하지 않습니다. 두 번째 호출이 수행합니다. 그것의 끝 부분에는 Foo 테이블에 하나의 행이 있습니다.

내가 한 번 후자의 방법 호출 같은 클래스에 두 번째 JUnit 테스트가 있습니다

@Test 
public void dupTestOneTransaction() { 
    contextTestHelper.deleteAllFoo(); 
    Assert.assertEquals(0, contextTestHelper.getCountOfFoo()); 

    boolean hadException = false; 
    try { 
     contextTestHelper.createTwoFoos(DUPLICATE_VALUE); 
    } catch (ContextDuplicationException th) { 
     hadException = true; 
    } 
    Assert.assertEquals(hadException, true); 
    Assert.assertEquals(0, contextTestHelper.getCountOfFoo()); 
} 

이 두 번째 테스트는 최종 주장 실패 - 난 동안 푸 인스턴스의 수는, 1 0을 기대하고 있습니다.

코드가 JBoss에서 실행될 때 JNDI 조회를 사용하려고하기 때문에 데이터 소스 설정에 약간의 헛소리가 있습니다. 여기

@BeforeClass 
public static void setUpClass() throws NamingException { 
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml"); 
    DataSource testDataSource = (DataSource) context.getBean("testDataSource"); 
    SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); 
    builder.bind("java:comp/env/jdbc/dataSource", testDataSource); 
    builder.activate(); 
} 

는 넷빈즈에서 테스트 패키지 '기본 패키지에서 설정 내 스프링 test.xml의 파일입니다 : 그 결과, JUnit을이 장면 뒤에 JNDI 룩업 (ContextTest.java)를 설정해야 :

<?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:tx="http://www.springframework.org/schema/tx" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
     http://www.springframework.org/schema/tx 
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 
     http://www.springframework.org/schema/context 
     http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 

<context:property-placeholder location="user-specific.properties"/> 

<bean id="testDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
    <property name="driverClassName"><value>${db.driver.class}</value></property> 
    <property name="url"><value>${db.url}</value></property> 
    <property name="username"><value>${db.user}</value></property> 
    <property name="password"><value>${db.password}</value></property> 
</bean> 
</beans> 

첫 번째 테스트가 작동 한 이후 분명히 데이터베이스에 연결할 수 있으므로 여기에 특별한 문제가 있다고 생각하지 않습니다.

<?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" 
     xmlns:context="http://www.springframework.org/schema/context" 
     xmlns:jee="http://www.springframework.org/schema/jee" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd 
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd 
     http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd" 
     default-autowire="byName" > 

    <context:annotation-config /> 
    <context:component-scan base-package="com.xyzzy" /> 
    <tx:annotation-driven transaction-manager="transactionManager" /> 

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
     <property name="location"> 
      <value>classpath:user-specific.properties</value> 
     </property> 
    </bean> 

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/dataSource"/> 

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
     <property name="dataSource" ref="dataSource" /> 
    </bean> 

    <bean id="fooDAO" class="com.xyzzy.FooDAOImpl" /> 

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> 
     <property name="dataSource"><ref local="dataSource"/></property> 
     <property name="packagesToScan" value="com.xyzzy" /> 
     <property name="hibernateProperties"> 
      <props> 
       <prop key="hibernate.dialect">${db.dialect}</prop> 
       <prop key="hibernate.show_sql">${db.show_sql}</prop> 
       <prop key="hibernate.hbm2ddl.auto">${db.hbm2ddl.auto}</prop> 
      </props> 
     </property> 
    </bean> 

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
     <property name="dataSource" ref="dataSource"/> 
     <property name="jpaVendorAdapter"> 
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
       <property name="databasePlatform" value="${db.dialect}"/> 
       <property name="generateDdl" value="true"/> 
       <property name="showSql" value="true"/> 

      </bean> 
     </property> 
     <property name="packagesToScan" value="com.xyzzy" /> 
    </bean> 
</beans> 

의 모든되지-전적으로를위한 테스트 클래스 (푸, FooDAO, FooDAOImpl) 패키지 com.xyzzy에, 그리고 테스트들 모두를 (하여 ContextTest : 여기

은 applicationContext.xml이다 , ContextTestHelper, ContextTestHelperImpl, ContextDuplicationException)가 com.xyzzy.test에 있습니다.

Foo, FooDAO 또는 FooDAOImpl에는 @Transactional 어노테이션이 없습니다. ContextTestHelperImpl은 트랜잭션 경계를 지정하는 유일한 객체입니다.

정상적으로 동작하도록 수정하는 방법에 대한 제안 사항은 무엇입니까? (dataSource 클래스 또는 transactionManager를 선택하는 데 문제가 있습니까? applicationContext.xml의 일부 설정이 일관성이 없거나 중복되어 있습니까?)

UPDATE :

DAO 구현 클래스 :

또한 Propagation.REQUIRED (스레드와 연관된 트랜잭션이 아니라면 조기에 예외가 발생됩니다 선가와 그것을 시도
@Repository("FooDAO") 
public class FooDAOImpl implements FooDAO { 
    private HibernateTemplate hibernateTemplate; 

    @Autowired 
    public void setSessionFactory(SessionFactory sessionFactory) { 
     this.hibernateTemplate = new HibernateTemplate(sessionFactory); 
    } 

    @Override 
    public Foo createFoo(int val) { 
     Foo foo = new Foo(); 
     foo.setVal(val); 
     saveFoo(foo); 
     return foo; 
    } 

    void saveFoo(Foo foo) { 
     hibernateTemplate.save(foo); 
    } 

    [... and many similar methods ...] 

),하지만 그것의 행동을 변경하지 않습니다.

답변

0

나는 당신이 수업에서 2 번의 테스트를 받았다고 생각합니다. 해당 테스트가 순차적으로 실행된다고 가정합니다. 귀하는 1 또는 0의 정확한 레코드 수를 기대합니다. 테스트는 병렬로 실행되므로 예상 결과와 충돌합니다.

각 테스트가 고유 한 값 (삽입)을 사용하도록 논리를 변경하십시오. select all (기준 없음)에 기반한 레코드 수를 비교하는 대신. 기준이있는 데이터베이스에서 선택하십시오 (where 절 추가).

요약하면 테스트 러너는 테스트를 병렬로 실행합니다. 테스트 케이스를 고려하고 분리하기 위해이 작업을 수행해야합니다. 각 테스트 케이스마다 고유 한 값을 사용하십시오.

희망이 있습니다.

+0

답장을 보내 주셔서 감사합니다.하지만 문제가 아닌 것으로 확신합니다. 각 방법에 대한 항목/종료를 인쇄 할 때 테스트가 순차적으로 실행되는 것으로 보입니다. (당신이 제안한대로 두 번째 테스트를 다른 고유 한 값을 사용하도록 변경했으며, 여전히 같은 방식으로 실패했습니다.) – user3207820

+0

응답 해 주셔서 감사합니다. 나는 전제에 도전 할 것이다 : return new Foo[] { fooDAO.createFoo(val), fooDAO.createFoo(val) };는 동일한 거래에 2 개의 삽입을 수행한다. 그게 당신 DAO에서 발생하지 않을까요 ?? like : entityManager.persist(s); entityManager.persist(s);. 전제를 테스트하기 위해 디버거를 통해 코드를 실행합니다. 데이터베이스는 두 번째 삽입을 거부합니다. 따라서 전적으로 2 개의 삽입에 대한 전제는 단일 트랜잭션에서 발생합니다. 따라서 단일 트랜잭션이 두 삽입을 모두 수행하는지 확인합니다. 행운을 빕니다. – lorinpa

+0

죄송합니다. 오타. 내 의견을 읽어야한다 : "그래서 귀하의 전제는 전적으로 하나의 트랜잭션에서 발생하는 2 개의 삽입에 의존합니다.". 전제는 데이터베이스가 아닌 트랜잭션 관리자가 초기 삽입을 롤백합니다. – lorinpa

관련 문제