Improve transaction management for @Sql scripts
Prior to this commit, the support for SQL script execution via @Sql provided an algorithm for looking up a required PlatformTransactionManager to use to drive transactions. However, a transaction manager is not actually required for all testing scenarios. This commit improves the transaction management support for @Sql so that SQL scripts can be executed without a transaction if a transaction manger is not present in the ApplicationContext. The updated algorithm now supports the following use cases. - If a transaction manager and data source are both present (i.e., explicitly specified via the transactionManager and dataSource attributes of @SqlConfig or implicitly discovered in the ApplicationContext based on conventions), both will be used. - If a transaction manager is not explicitly specified and not implicitly discovered based on conventions, SQL scripts will be executed without a transaction but requiring the presence of a data source. If a data source is not present, an exception will be thrown. - If a data source is not explicitly specified and not implicitly discovered based on conventions, an attempt will be made to retrieve it by using reflection to invoke a public method named getDataSource() on the transaction manager. If this attempt fails, an exception will be thrown. - If a data source can be retrieved from the resolved transaction manager using reflection, an exception will be thrown if the resolved data source is not the data source associated with the resolved transaction manager. This helps to avoid possibly unintended configuration errors. - If @SqlConfig.transactionMode is set to ISOLATED, an exception will be thrown if a transaction manager is not present. Issue: SPR-11911
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.jdbc.JdbcTestUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.transaction.TransactionTestUtils.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link Sql @Sql} support with only a {@link DataSource}
|
||||
* present in the context (i.e., no transaction manager).
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@ContextConfiguration
|
||||
@Sql({ "schema.sql", "data.sql" })
|
||||
@DirtiesContext
|
||||
public class DataSourceOnlySqlScriptsTests {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
@Autowired
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
// test##_ prefix is required for @FixMethodOrder.
|
||||
public void test01_classLevelScripts() {
|
||||
assertInTransaction(false);
|
||||
assertNumUsers(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql({ "drop-schema.sql", "schema.sql", "data.sql", "data-add-dogbert.sql" })
|
||||
// test##_ prefix is required for @FixMethodOrder.
|
||||
public void test02_methodLevelScripts() {
|
||||
assertInTransaction(false);
|
||||
assertNumUsers(2);
|
||||
}
|
||||
|
||||
protected void assertNumUsers(int expected) {
|
||||
assertEquals("Number of rows in the 'user' table.", expected,
|
||||
JdbcTestUtils.countRowsInTable(jdbcTemplate, "user"));
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()//
|
||||
.setName("empty-sql-scripts-without-tx-mgr-test-db")//
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.transaction.TransactionTestUtils.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link Sql @Sql} that verify support for inferring
|
||||
* {@link DataSource}s from {@link PlatformTransactionManager}s.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see InferredDataSourceTransactionalSqlScriptsTests
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@DirtiesContext
|
||||
public class InferredDataSourceSqlScriptsTests {
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource1;
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource2;
|
||||
|
||||
|
||||
@Test
|
||||
@Sql(scripts = "data-add-dogbert.sql", config = @SqlConfig(transactionManager = "txMgr1"))
|
||||
public void database1() {
|
||||
assertInTransaction(false);
|
||||
assertUsers(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql(scripts = "data-add-catbert.sql", config = @SqlConfig(transactionManager = "txMgr2"))
|
||||
public void database2() {
|
||||
assertInTransaction(false);
|
||||
assertUsers(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
|
||||
}
|
||||
|
||||
private void assertUsers(JdbcTemplate jdbcTemplate, String... users) {
|
||||
List<String> expected = Arrays.asList(users);
|
||||
Collections.sort(expected);
|
||||
List<String> actual = jdbcTemplate.queryForList("select name from user", String.class);
|
||||
Collections.sort(actual);
|
||||
assertEquals("Users in database;", expected, actual);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager txMgr1() {
|
||||
return new DataSourceTransactionManager(dataSource1());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager txMgr2() {
|
||||
return new DataSourceTransactionManager(dataSource2());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource1() {
|
||||
return new EmbeddedDatabaseBuilder()//
|
||||
.setName("database1")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource2() {
|
||||
return new EmbeddedDatabaseBuilder()//
|
||||
.setName("database2")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.springframework.test.transaction.TransactionTestUtils.*;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Exact copy of {@link InferredDataSourceSqlScriptsTests}, except that test
|
||||
* methods are transactional.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see InferredDataSourceSqlScriptsTests
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@DirtiesContext
|
||||
public class InferredDataSourceTransactionalSqlScriptsTests {
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource1;
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource2;
|
||||
|
||||
|
||||
@Test
|
||||
@Transactional("txMgr1")
|
||||
@Sql(scripts = "data-add-dogbert.sql", config = @SqlConfig(transactionManager = "txMgr1"))
|
||||
public void database1() {
|
||||
assertInTransaction(true);
|
||||
assertUsers(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional("txMgr2")
|
||||
@Sql(scripts = "data-add-catbert.sql", config = @SqlConfig(transactionManager = "txMgr2"))
|
||||
public void database2() {
|
||||
assertInTransaction(true);
|
||||
assertUsers(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
|
||||
}
|
||||
|
||||
private void assertUsers(JdbcTemplate jdbcTemplate, String... users) {
|
||||
List<String> expected = Arrays.asList(users);
|
||||
Collections.sort(expected);
|
||||
List<String> actual = jdbcTemplate.queryForList("select name from user", String.class);
|
||||
Collections.sort(actual);
|
||||
assertEquals("Users in database;", expected, actual);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager txMgr1() {
|
||||
return new DataSourceTransactionManager(dataSource1());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager txMgr2() {
|
||||
return new DataSourceTransactionManager(dataSource2());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource1() {
|
||||
return new EmbeddedDatabaseBuilder()//
|
||||
.setName("database1")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource2() {
|
||||
return new EmbeddedDatabaseBuilder()//
|
||||
.setName("database2")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
|
||||
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,9 +18,14 @@ package org.springframework.test.context.jdbc;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
@@ -58,17 +63,46 @@ public class SqlScriptsTestExecutionListenerTests {
|
||||
public void valueAndScriptsDeclared() throws Exception {
|
||||
Class<?> clazz = ValueAndScriptsDeclared.class;
|
||||
Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(clazz);
|
||||
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("valueAndScriptsDeclared"));
|
||||
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
|
||||
|
||||
assertExceptionContains("Only one declaration of SQL script paths is permitted");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isolatedTxModeDeclaredWithoutTxMgr() throws Exception {
|
||||
ApplicationContext ctx = mock(ApplicationContext.class);
|
||||
when(ctx.getResource(anyString())).thenReturn(mock(Resource.class));
|
||||
when(ctx.getAutowireCapableBeanFactory()).thenReturn(mock(AutowireCapableBeanFactory.class));
|
||||
|
||||
Class<?> clazz = IsolatedWithoutTxMgr.class;
|
||||
Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(clazz);
|
||||
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
|
||||
when(testContext.getApplicationContext()).thenReturn(ctx);
|
||||
|
||||
assertExceptionContains("cannot execute SQL scripts using Transaction Mode [ISOLATED] without a PlatformTransactionManager");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingDataSourceAndTxMgr() throws Exception {
|
||||
ApplicationContext ctx = mock(ApplicationContext.class);
|
||||
when(ctx.getResource(anyString())).thenReturn(mock(Resource.class));
|
||||
when(ctx.getAutowireCapableBeanFactory()).thenReturn(mock(AutowireCapableBeanFactory.class));
|
||||
|
||||
Class<?> clazz = MissingDataSourceAndTxMgr.class;
|
||||
Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(clazz);
|
||||
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
|
||||
when(testContext.getApplicationContext()).thenReturn(ctx);
|
||||
|
||||
assertExceptionContains("supply at least a DataSource or PlatformTransactionManager");
|
||||
}
|
||||
|
||||
private void assertExceptionContains(String msg) throws Exception {
|
||||
try {
|
||||
listener.beforeTestMethod(testContext);
|
||||
fail("Should have thrown an IllegalStateException.");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// System.err.println(e.getMessage());
|
||||
assertTrue("Exception message should contain: " + msg, e.getMessage().contains(msg));
|
||||
}
|
||||
}
|
||||
@@ -93,7 +127,21 @@ public class SqlScriptsTestExecutionListenerTests {
|
||||
static class ValueAndScriptsDeclared {
|
||||
|
||||
@Sql(value = "foo", scripts = "bar")
|
||||
public void valueAndScriptsDeclared() {
|
||||
public void foo() {
|
||||
}
|
||||
}
|
||||
|
||||
static class IsolatedWithoutTxMgr {
|
||||
|
||||
@Sql(scripts = "foo.sql", config = @SqlConfig(transactionMode = TransactionMode.ISOLATED))
|
||||
public void foo() {
|
||||
}
|
||||
}
|
||||
|
||||
static class MissingDataSourceAndTxMgr {
|
||||
|
||||
@Sql("foo.sql")
|
||||
public void foo() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user