Использование Spring TransactionManager для интеграционного теста

#java #spring #spring-test

#java #spring #spring-тест

Вопрос:

Я уже некоторое время бьюсь головой об это, и у меня такое чувство, что я упускаю из виду что-то глупое. Я пытаюсь использовать диспетчер транзакций для очень простого интеграционного теста, но, похоже, не могу заставить его выполнить откат, как я думаю, это должно быть.

Тестируемый класс:

 public class JdbcUserDAO implements UserDAO {

@Autowired
private DataSource dataSource;

private static final String TERM_USER_SQL =
        "UPDATE USER "  
        "SET TERM_DATE = date('now') "  
        "WHERE USER_NAME = ?";

@Override
public void terminateUser(String userName) {

    try (Connection conn = dataSource.getConnection()){
        PreparedStatement stmt = conn.prepareStatement(TERM_USER_SQL);
        stmt.setString(1, userName);
        stmt.executeUpdate();
    } catch (SQLException e) {
        throw new RuntimeException("Caught SQLException in persistence service", e);
    }
}
  

Контекст 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: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/tx
                       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

<tx:annotation-driven/>
<bean id="userDAO" class="com.mycompany.persistence.dao.impl.JdbcUserDAO"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.sqlite.JDBC"/>
    <property name="url" value="jdbc:sqlite:C:\development.sqlite3"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
  

Тестовый класс:

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/applicationContext.xml"})
@TransactionConfiguration
@Transactional
public class JdbcUserDAOTest {

    @Autowired
    private UserDAO userDAO;

    @Test
    public void test_terminateUser() {
        userDAO.terminateUser("USERNAME");
    }
}
  

AFAIK, поведение обратного управления по умолчанию для конфигурации TransactionConfiguration равно «true», а имя компонента TransactionManager по умолчанию равно «TransactionManager», поэтому я должен быть установлен там. Я запускаю этот тест и ожидаю, что столбец «TERM_DATE» вернется к «null» (его начальному значению) после завершения теста, но это не так (я запрашиваю после завершения теста и вижу, что он все еще = сегодняшняя дата).

Я даже получаю то, что кажется очень многообещающим — ведение журнала:

 Jun 17, 2014 1:25:48 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction
INFO: Began transaction (1): transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@10279954]; rollback [true]
Jun 17, 2014 1:25:48 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction
INFO: Rolled back transaction after test execution for test context [TestContext@41cbd50f testClass = JdbcUserDAOTest, testInstance = com.mycompany.persistence.dao.JdbcUserDAOTest@2d10ed88, testMethod = test_terminateUser@JdbcUserDAOTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@325a92d5 testClass = JdbcUserDAOTest, locations = '{classpath:/applicationContext.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]
Jun 17, 2014 1:25:48 PM org.springframework.context.support.GenericApplicationContext doClose
INFO: Closing org.springframework.context.support.GenericApplicationContext@3304e92a: startup date [Tue Jun 17 13:25:48 MDT 2014]; root of context hierarchy
  

Какого черта я упускаю из виду??? Заранее большое спасибо.

Если это поможет, соответствующие зависимости POM выглядят следующим образом:

 <dependencies>
    <dependency>
        <groupId>org.xerial</groupId>
        <artifactId>sqlite-jdbc</artifactId>
        <version>3.7.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-core</artifactId>
        <version>1.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>3.2.9.RELEASE</spring.version>
</properties>
  

Комментарии:

1. Просто для удовольствия, вы готовы поменять SQLite на H2 или HSQL? Я сомневаюсь, что это проблема, но убедиться в этом не повредит 🙂

2. Забавно, что вы упомянули об этом — реальный код использует Oracle, но я переключился на SQLite, чтобы исключить это из уравнения! 🙂

Ответ №1:

Если вы используете Spring, пожалуйста, не пишите шаблонный код JDBC.

Вместо этого используйте Spring JdbcTemplate , который автоматически работает с текущей транзакцией, управляемой Spring, и устраняет весь шаблонный код JDBC.

Вы должны заменить всю свою реализацию DAO чем-то вроде этого:

 public class JdbcUserDAO {

    private static final String TERM_USER_SQL = "UPDATE USER "
              "SET TERM_DATE = date('now') WHERE USER_NAME = ?";

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public JdbcUserDAO(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void terminateUser(String userName) {
        jdbcTemplate.update(TERM_USER_SQL, userName);
    }

}
  

С уважением,

Сэм

Комментарии:

1. Спасибо тебе, Сэм, ты справился с задачей. Печально то, что я действительно знаю лучше, чем вручную запускать JDBC, но у меня действительно были шоры. : ( Я потратил <15 минут на рефакторинг до JdbcTemplate, и это сработало как шарм. Если кто-то использует приведенное выше в качестве примера, мой готовый код выглядит почти идентично коду Сэма — единственное, что я изменил, это избавился от тега «управляемый аннотациями» из контекста. Еще раз спасибо!

Ответ №2:

Используйте DataSourceUtils.getConnection(dataSource) вместо dataSource.getConnection() . Первый подготавливает соединение для поддержки транзакций, в то время как второй просто обходит транзакционную инфраструктуру Spring и требует от вас обработки деталей транзакции.