Hi Spring team
I would like to report a behaviour I encoutered using TransactionAwareDataSourceProxy in my tests, which I think may be a bug.
I used Spring Boot 4.0.3 but I believe any 4.0.X and earlier versions exhibit the same behaviour (see below).
I have a bunch of DB related sliced tests. A mix of @JdbcTest and @DataJdbcTest to be specific.
- Flyway to initialize the database schema
- Many of the tests use
@Sql to prepare Data
- I do assertions on the state of the database using the AssertJ DB library
This is not directly related to AssertJ DB, but the way I use it led me to encounter the problem.
The AssertJ DB library is (obviously) an AssertJ-like library providing assertions on the database.
It needs a DataSource as an input. And since it is not a Spring library, it is not aware of the Spring transaction.
My current use of it is to wrap the DataSource with TransactionAwareDataSourceProxy for every call to the library; and then perform the DB assertions.
It works fine, but I wanted to know if I could avoid doing the wrapping on each call, by wrapping the autoconfigured DataSource only once with the proxy at the context level.
The Javadoc for BeanPostProcessor made it look like a good fit (the postProcessAfterInitialization part) :
Typically, post-processors that populate beans via marker interfaces or the like will implement postProcessBeforeInitialization, while post-processors that wrap beans with proxies will normally implement postProcessAfterInitialization.
This looks like this :
@SpringBootApplication
public class DsTxAwareApp {
public static void main(String[] args) {
SpringApplication.run(DsTxAwareApp.class, args);
}
@Bean
static TxAwareDataSourceBeanPostProcessor txAwareDataSourceBeanPostProcessor() {
return new TxAwareDataSourceBeanPostProcessor();
}
static class TxAwareDataSourceBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSource targetDataSource) {
return new TransactionAwareDataSourceProxy(targetDataSource);
}
return bean;
}
}
}
After that, I was expecting that any interaction with the DataSource would be aware of the Spring transaction, without having to do the wrapping again.
It works in some cases, however any tests using @Sql fails with something like this :
java.lang.IllegalStateException: Failed to execute SQL scripts for test context [...]
the configured DataSource [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy] (named '') is not the one associated with transaction manager [org.springframework.jdbc.support.JdbcTransactionManager] (named '')
at org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener.executeSqlScripts(SqlScriptsTestExecutionListener.java:355)
This happens when executing the @Sql, so independently of any use of the AssertJ DB Library; hence unrelated.
Here is a simplified version of the test, using @Sql and raw Jdbc Usage (no AssertJ DB) :
@JdbcTest(includeFilters = @Filter(classes = FooJdbcDao.class, type = ASSIGNABLE_TYPE))
class FooJdbcDaoTest {
@Autowired
private FooJdbcDao underTest;
@Autowired
private DataSource dataSource;
@Test
@Sql(statements = "insert into FOO (ID, BAR) values (12, 'dummy')")
void testWithExistingRow() {
assertThat(count()).isEqualTo(1);
Foo foo = Foo.builder()
.id(17)
.bar("whatever")
.build();
underTest.insertFoo(foo);
assertThat(count()).isEqualTo(2);
}
private int count() {
try (
Connection con = dataSource.getConnection();
PreparedStatement statement = con.prepareStatement("select count(*) as count from foo");
ResultSet result = statement.executeQuery()
) {
result.next();
return result.getInt("count");
} catch (SQLException e) {
throw new RuntimeException("Error during count");
}
}
}
My understanding is that it happens in the SqlScriptsTestExecutionListener, at a point where there is some logic to match a DataSource against its associated PlatformTransactionManager. Maybe it has to do with apps using several DataSource (just for my knowledge) ?
Now when trying the BeanPostProcessor approach, I saw that in the DataSourceTransactionManager there is a need to check for such a proxy.
I then assumed that there could be other places in the framework where that was necessary; but maybe my use case was never intended to be supported.
I put up a reproducer on my gitlab : https://gitlab.com/jmaniquet-prototypes/data-source-tx-aware
main branch : @JdbcTest / @Sql - no BeanPostProcessor, verifies data by raw Jdbc, wrapping the DataSource on each call interacting directly with
tx-aware-proxy-with-bean-post-processor branch : remove the wrapping on each call, and use the BeanPostProcessor - @Sql then tests starts failing
older-spring-boot - Same with older versions of Spring Boot - I wanted to see if at some point it used to work (it did not)
There is an additional branch with more complex set up which may be to much for a reproducer : AssertJ DB is introduced and there is additional tests for @DataJdbcTest and @MybatisTest (integration with mybatis).
I wanted to know if other sliced tests had the same behaviour (they do).
This may be a spring only problem; not boot; I opened the ticket here, my setup being boot only.
If it is not intended to be supported, maybe this should be documented somewhere ?
Let me know if you need me to prune the repo or alter it (or if maybe I am doing something wrong).
Thanks for this amazing framework
Related Issues
Hi Spring team
I would like to report a behaviour I encoutered using
TransactionAwareDataSourceProxyin my tests, which I think may be a bug.I used Spring Boot 4.0.3 but I believe any 4.0.X and earlier versions exhibit the same behaviour (see below).
I have a bunch of DB related sliced tests. A mix of @
JdbcTestand@DataJdbcTestto be specific.@Sqlto prepare DataThis is not directly related to AssertJ DB, but the way I use it led me to encounter the problem.
The AssertJ DB library is (obviously) an AssertJ-like library providing assertions on the database.
It needs a
DataSourceas an input. And since it is not a Spring library, it is not aware of the Spring transaction.My current use of it is to wrap the
DataSourcewithTransactionAwareDataSourceProxyfor every call to the library; and then perform the DB assertions.It works fine, but I wanted to know if I could avoid doing the wrapping on each call, by wrapping the autoconfigured
DataSourceonly once with the proxy at the context level.The Javadoc for
BeanPostProcessormade it look like a good fit (thepostProcessAfterInitializationpart) :This looks like this :
After that, I was expecting that any interaction with the
DataSourcewould be aware of the Spring transaction, without having to do the wrapping again.It works in some cases, however any tests using
@Sqlfails with something like this :This happens when executing the
@Sql, so independently of any use of the AssertJ DB Library; hence unrelated.Here is a simplified version of the test, using
@Sqland raw Jdbc Usage (no AssertJ DB) :My understanding is that it happens in the
SqlScriptsTestExecutionListener, at a point where there is some logic to match aDataSourceagainst its associatedPlatformTransactionManager. Maybe it has to do with apps using severalDataSource(just for my knowledge) ?Now when trying the BeanPostProcessor approach, I saw that in the
DataSourceTransactionManagerthere is a need to check for such a proxy.I then assumed that there could be other places in the framework where that was necessary; but maybe my use case was never intended to be supported.
I put up a reproducer on my gitlab : https://gitlab.com/jmaniquet-prototypes/data-source-tx-aware
mainbranch :@JdbcTest/@Sql- no BeanPostProcessor, verifies data by raw Jdbc, wrapping theDataSourceon each call interacting directly withtx-aware-proxy-with-bean-post-processorbranch : remove the wrapping on each call, and use theBeanPostProcessor-@Sqlthen tests starts failingolder-spring-boot- Same with older versions of Spring Boot - I wanted to see if at some point it used to work (it did not)There is an additional branch with more complex set up which may be to much for a reproducer : AssertJ DB is introduced and there is additional tests for
@DataJdbcTestand@MybatisTest(integration with mybatis).I wanted to know if other sliced tests had the same behaviour (they do).
This may be a spring only problem; not boot; I opened the ticket here, my setup being boot only.
If it is not intended to be supported, maybe this should be documented somewhere ?
Let me know if you need me to prune the repo or alter it (or if maybe I am doing something wrong).
Thanks for this amazing framework
Related Issues