2014-09-02 44 views
2

我有2個插入/更新語句,我想在Spring事務中包裝。如果兩個語句都失敗了,我想讓語句回滾。我試着用下面的場景來測試這一點:JDBC模板的彈簧回滾問題

我有一個application具有DB約束它的CODE只能是最多25個字符。我強制違反約束,同時通過提供太大的CODE來節省第二個application

我期望第一次插入/更新application A會被回滾。但是,我的測試表明,情況並非如此。

我不太清楚,我缺少的是什麼?

我TRANSATION測試:

public class SpringTransactionTest { 

private static final Logger LOGGER = LoggerFactory.getLogger(SpringTransactionTest.class.getName()); 

private ApplicationDAO applicationDAO = SpringUtils4Test.getBean(ApplicationDAO.class); 

@Transactional 
public void doStuff() { 
    Application a = new Application(); 
    a.setCode("A"); 
    Application b = new Application(); 
    b.setCode("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); 
    a = applicationDAO.insertOrUpdate(a); 
    LOGGER.debug("a: "+a.getIdentifier()); 

    b = applicationDAO.insertOrUpdate(b); 
    LOGGER.debug("b: "+b.getIdentifier()); 
} 

@Before 
public void cleanIt() { 
    Application a = applicationDAO.getApplicationByCode("A"); 
    if(a != null && a.getIdentifier() > 0) { 
     applicationDAO.deleteById(a.getIdentifier()); 
     LOGGER.debug("removed A"); 
    } 
    Application b = applicationDAO.getApplicationByCode("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); 
    if(b != null && b.getIdentifier() > 0) { 
     applicationDAO.deleteById(b.getIdentifier()); 
     LOGGER.debug("removed B"); 
    } 
} 

@Test 
public void runTransactionTest() { 
    try { 
     doStuff(); 
    } catch (org.springframework.jdbc.UncategorizedSQLException e) { 
     e.printStackTrace(); 
    } 

    Assert.assertNull("A should be null, but instead A was found", applicationDAO.getApplicationByCode("A")); 
    Assert.assertNull("B should be null, but instead B was found", applicationDAO.getApplicationByCode("B")); 
} 
} 

控制檯輸出:

16:42:38.272 [main] DEBUG bla.dao.AbstractDAO - getApplicationDetails for : A 
16:42:38.779 [main] DEBUG bla.dao.AbstractDAO - application found with id: 1008 
16:42:39.062 [main] DEBUG bla.view.SpringTransactionTest - removed A 
16:42:39.062 [main] DEBUG bla.dao.AbstractDAO - getApplicationDetails for : BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 
16:42:39.358 [main] DEBUG bla.dao.AbstractDAO - no application found 
16:42:39.676 [main] DEBUG bla.dao.AbstractDAO - new application id: 1009 
16:42:39.677 [main] DEBUG bla.view.SpringTransactionTest - a: 1009 
02-Sep-2014 16:42:40 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 
INFO: Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml] 
02-Sep-2014 16:42:40 org.springframework.jdbc.support.SQLErrorCodesFactory <init> 
INFO: SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase] 
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL []; SQL state [72000]; error code [12899]; ORA-12899: value too large for column "BLA"."APPLICATION"."CODE" (actual: 36, maximum: 25) 
; nested exception is java.sql.SQLException: ORA-12899: value too large for column "BLA"."APPLICATION"."CODE" (actual: 36, maximum: 25) 
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84) 
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) 
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) 
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658) 
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:941) 
at bla.dao.ApplicationDAO.insert(ApplicationDAO.java:98) 
at bla.dao.ApplicationDAO.insertOrUpdate(ApplicationDAO.java:90) 
at bla.view.SpringTransactionTest.doStuff(SpringTransactionTest.java:35) 
at bla.view.SpringTransactionTest.runTransactionTest(SpringTransactionTest.java:60) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
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.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) 
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) 
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.junit.runners.ParentRunner.run(ParentRunner.java:309) 
at org.junit.runner.JUnitCore.run(JUnitCore.java:160) 
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77) 
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195) 
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) 
Caused by: java.sql.SQLException: ORA-12899: value too large for column "BLA"."APPLICATION"."CODE" (actual: 36, maximum: 25) 
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112) 
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331) 
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288) 
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:745) 
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:216) 
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:966) 
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1170) 
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3339) 
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3423) 
at org.springframework.jdbc.core.JdbcTemplate$3.doInPreparedStatement(JdbcTemplate.java:944) 
at org.springframework.jdbc.core.JdbcTemplate$3.doInPreparedStatement(JdbcTemplate.java:941) 
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:642) 
... 31 more 
16:42:40.411 [main] DEBUG bla.dao.AbstractDAO - getApplicationDetails for : A 
16:42:40.717 [main] DEBUG bla.dao.AbstractDAO - application found with id: 1009 
java.lang.AssertionError: A should be null, but instead A was found expected null, but was:<[email protected]> 
at org.junit.Assert.fail(Assert.java:88) 
at org.junit.Assert.failNotNull(Assert.java:664) 
at org.junit.Assert.assertNull(Assert.java:646) 
at bla.view.SpringTransactionTest.runTransactionTest(SpringTransactionTest.java:67) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
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.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) 
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) 
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.junit.runners.ParentRunner.run(ParentRunner.java:309) 
at org.junit.runner.JUnitCore.run(JUnitCore.java:160) 
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77) 
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195) 
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) 

Process finished with exit code -1 

Maven的依賴屬性:

<java.version>1.6</java.version> 
<spring.version>4.0.0.RELEASE</spring.version> 
<jsf.version>2.2.5</jsf.version> 
<servlet.version>3.0.1</servlet.version> 
<ojdbc14.version>10.2.0.3.0</ojdbc14.version> 

SpringUtils4Test:

public class SpringUtils4Test { 

    private static final ClassPathXmlApplicationContext CTX = new ClassPathXmlApplicationContext("applicationContext.xml"); 

    public static <T> T getBean(Class<T> clazz) { 
     return CTX.getBean(clazz); 
    } 
} 

ApplicationDAO:

public Application insertOrUpdate(final Application application) { 
    if (application.getIdentifier() == null) { 
     return insert(application); 
    } else { 
     return update(application); 
    } 
} 

private Application insert(final Application application) { 
    KeyHolder keyHolder = new GeneratedKeyHolder(); 
    jdbcTemplate.update(
      new PreparedStatementCreator() { 
       public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { 
        PreparedStatement ps = connection.prepareStatement(
         "insert into application (CODE) values (?)", 
         new String[]{"id"}); 
        JdbcUtil.setValues(ps, application.getCode()); 
        return ps; 
       } 
      }, 
      keyHolder); 

    final Long applicationId = keyHolder.getKey().longValue(); 
    LOGGER.debug("new application id: " + applicationId); 
    application.setIdentifier(applicationId); 
    return application; 
} 

private Application update(final Application app) { 
    jdbcTemplate.update("update APPLICATION set CODE = ? where id = ?", 
      app.getCode(), app.getIdentifier()); 
    return app; 
} 

我的ApplicationContext:

<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context" 
    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.xsd 
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 
    "> 

<context:component-scan base-package="bla"/> 
<aop:aspectj-autoproxy proxy-target-class="true"/> 

<bean id="transactionManager" 
     class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="dataSource" ref="dataSource" /> 
</bean> 

<!-- Enable Annotation based Declarative Transaction Management --> 
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" /> 

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> 
    <property name="url" value="jdbc:oracle:thin:@bla:1540:bla"/> 
    <property name="username" value="bla"/> 
    <property name="password" value="blabla"/> 
</bean> 

</beans> 
+0

SpringUtils4Test那是什麼? – 2014-09-02 15:39:31

+0

你使用的是什麼春季版本? – 2014-09-02 15:49:37

回答

1

我覺得@Transactional在JUnit測試爲時已晚

你需要把它移動到DAO(或創建業務對象層)

當春天創建ctx.getBean,它需要代理該類申請交易

看看這些幫助 Does Spring @Transactional attribute work on a private method?

http://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

+0

我實際上正在嘗試解決業務服務方法上的@Transactional註釋問題。我注意到它沒有像預期的那樣工作,所以我試圖爲類似的東西創建單元測試 – Filip 2014-09-02 16:03:53

+0

你是對的。在我的UT之外和SpringBean內部移動「doStuff」方法時,事務似乎可行。 我想這是由於我的UT不是SPringBean,因此不支持@Transaction註解 – Filip 2014-09-03 09:02:27