Tyvrel Tyvrel - 4 days ago 7
Java Question

Spring-test's @Rollback doesn't rollback anything

Working code below:

//import everything

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Test2.TestConfiguration.class)
@Transactional
public class Test2 {

@Autowired
private DataSource datasource;

@BeforeTransaction
public void createDatabase() throws SQLException {
DataSourceUtils .getConnection(datasource)
.createStatement()
.execute("CREATE TABLE USERS (id bigint, size bigint, primary key (id))");
}

@Rollback
@Test
public void test() throws SQLException {
DataSourceUtils .getConnection(datasource)
.createStatement()
.execute("INSERT INTO USERS VALUES (5, 5)");
}

@AfterTransaction
public void dropTable() throws SQLException {
ResultSet rs = DataSourceUtils .getConnection(datasource)
.createStatement()
.executeQuery("SELECT * FROM USERS");
boolean isEmpty = !rs.next();
if (isEmpty) {
System.out.println("Rollback succeeded");
} else {
System.out.println("Rollback failed");
}
rs.close();

datasource .getConnection()
.createStatement()
.execute("DROP TABLE USERS");
}

@Configuration
public static class TestConfiguration {

@Bean
public DataSource driverManagerDataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
String dbURI = "database/tests/DerbyDB/db";
String connectionString = "jdbc:derby:" + dbURI;
if (!new File(dbURI).exists()) connectionString += ";create=true";
driverManagerDataSource.setUrl(connectionString);
driverManagerDataSource.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
return driverManagerDataSource;
}

@Bean
public PlatformTransactionManager platformTransactionManager() {
PlatformTransactionManager ptm = new DataSourceTransactionManager(driverManagerDataSource());
return ptm;
}
}
}


Solved question:
I want to rollback database state after each test using @Rollback annotation. Unfortunatelly it doesn't work.

Here is my test class:

//import everything

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Test2.TestConfiguration.class)
@Transactional
public class Test2 {

@Autowired
private DataSource datasource;

@BeforeTransaction
public void createTable() throws SQLException {
datasource .getConnection()
.createStatement()
.execute("CREATE TABLE USERS (id bigint, size bigint, primary key (id))");
}

@Rollback
@Test
public void test() throws SQLException {
datasource .getConnection()
.createStatement()
.execute("INSERT INTO USERS VALUES (5, 5)");
}

@AfterTransaction
public void dropTable() throws SQLException {
ResultSet rs = datasource .getConnection()
.createStatement()
.executeQuery("SELECT * FROM USERS");
boolean isEmpty = !rs.next();
if (isEmpty) {
System.out.println("Rollback succeeded");
} else {
System.out.println("Rollback failed");
}
rs.close();

datasource .getConnection()
.createStatement()
.execute("DROP TABLE USERS");
}

@Configuration
public static class TestConfiguration {

@Bean
public DataSource driverManagerDataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
String dbURI = "database/tests/DerbyDB/db";
String connectionString = "jdbc:derby:" + dbURI;
if (!new File(dbURI).exists()) connectionString += ";create=true";
driverManagerDataSource.setUrl(connectionString);
driverManagerDataSource.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
return driverManagerDataSource;
}

@Bean
public PlatformTransactionManager platformTransactionManager() {
PlatformTransactionManager ptm = new DataSourceTransactionManager(driverManagerDataSource());
return ptm;
}
}
}


Spring claims it rolled back database, which is not true, because record remains.

How do I make it work?

gru 01, 2016 12:26:14 PM org.springframework.test.context.transaction.TransactionContext startTransaction
INFO: Began transaction (1) for test context [DefaultTestContext@457c9034 testClass = Test2, testInstance = tyvrel.tastas.persistence.Test2@345f69f3, testMethod = test@Test2, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@e15b7e8 testClass = Test2, locations = '{}', classes = '{class tyvrel.tastas.persistence.Test2$TestConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@3bf9ce3e]; rollback [true]
gru 01, 2016 12:26:14 PM org.springframework.test.context.transaction.TransactionContext endTransaction
INFO: Rolled back transaction for test context [DefaultTestContext@457c9034 testClass = Test2, testInstance = tyvrel.tastas.persistence.Test2@345f69f3, testMethod = test@Test2, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@e15b7e8 testClass = Test2, locations = '{}', classes = '{class tyvrel.tastas.persistence.Test2$TestConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].
Rollback failed
gru 01, 2016 12:26:14 PM org.springframework.context.support.GenericApplicationContext doClose
INFO: Closing org.springframework.context.support.GenericApplicationContext@ae45eb6: startup date [Thu Dec 01 12:26:12 CET 2016]; root of context hierarchy


The full log is presented below:

gru 01, 2016 12:26:12 PM org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
INFO: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
gru 01, 2016 12:26:12 PM org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
INFO: Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@69d9c55, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@13a57a3b, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@7ca48474, org.springframework.test.context.support.DirtiesContextTestExecutionListener@337d0578, org.springframework.test.context.transaction.TransactionalTestExecutionListener@59e84876, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@61a485d2]
gru 01, 2016 12:26:12 PM org.springframework.context.support.GenericApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericApplicationContext@ae45eb6: startup date [Thu Dec 01 12:26:12 CET 2016]; root of context hierarchy
gru 01, 2016 12:26:13 PM org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init>
INFO: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
gru 01, 2016 12:26:13 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: org.apache.derby.jdbc.EmbeddedDriver
gru 01, 2016 12:26:14 PM org.springframework.test.context.transaction.TransactionContext startTransaction
INFO: Began transaction (1) for test context [DefaultTestContext@457c9034 testClass = Test2, testInstance = tyvrel.tastas.persistence.Test2@345f69f3, testMethod = test@Test2, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@e15b7e8 testClass = Test2, locations = '{}', classes = '{class tyvrel.tastas.persistence.Test2$TestConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@3bf9ce3e]; rollback [true]
gru 01, 2016 12:26:14 PM org.springframework.test.context.transaction.TransactionContext endTransaction
INFO: Rolled back transaction for test context [DefaultTestContext@457c9034 testClass = Test2, testInstance = tyvrel.tastas.persistence.Test2@345f69f3, testMethod = test@Test2, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@e15b7e8 testClass = Test2, locations = '{}', classes = '{class tyvrel.tastas.persistence.Test2$TestConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].
Rollback failed
gru 01, 2016 12:26:14 PM org.springframework.context.support.GenericApplicationContext doClose
INFO: Closing org.springframework.context.support.GenericApplicationContext@ae45eb6: startup date [Thu Dec 01 12:26:12 CET 2016]; root of context hierarchy

Answer

From what I understand, you're creating a new, independent Spring context manually in your test method, which during its initialization creates the table.

Since that context uses its own transaction manager and data source, it won't be affected by the @Rollback annotation - that one is processed in context of the (implicit) spring context defined for the whole test class.


Also note that in some DBs, you cannot rollback CREATE commands (not sure about Derby though).


Update

Another issue is that you're not actually using the transaction manager when you get the connection through datasource.getConnection().

From DataSourceTransactionManager documentation:

Application code is required to retrieve the JDBC Connection via DataSourceUtils.getConnection(DataSource) instead of a standard Java EE-style DataSource.getConnection() call. Spring classes such as JdbcTemplate use this strategy implicitly.

Comments