I have a straightforward code that saves something to the DB and then throws an exception to see if the changes get rollback.
ResourceA.java
@Autowire ServiceA serviceA;
@PUT
@Path("/{Id:[0-9] }")
public ObjectA updateSomethingResource(..) {
return serviceA.upateSomethingService(..);
}
ServiceA.java
@Transactional(rollbackFor=Exception.class)
public ObjectA upateSomethingService(EntitlementRequest entitlementRequest) throws ServiceException {
ObjectA objectA = getObjectFromDB(..);
objectA.setName("New Name");
dao.save(objectA)
throw new ServiceException("error"); //ServiceException extends Exception
}
When executing this and I check the DB the name of the object is still New Name
. I would have excepted it to be rolled back to what it was originally.
To understand what is happening I have tried looking at what log I can enable and the only one that I managed to enable is
logging.level.org.springframework.transaction.interceptor=TRACE
which gives:
TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [xxxxx.upateSomethingService]
TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [xxxxx.upateSomethingService] after exception: xxx.ServiceException
So the transaction manager does see the exception but nothing gets rollback.
Is there something I need to enable somewhere to have rollback actually working? What other log can I enable that could show me exactly what is happening?
UPDATE:
I have managed to show ore logs with logging.level.org.springframework=DEBUG
DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG o.s.j.s.JdbcTransactionManager - Creating new transaction with name [xxx.service.upateSomethingService]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception
DEBUG o.s.j.s.JdbcTransactionManager - Acquired Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root@localhost, MySQL Connector/J] for JDBC transaction
DEBUG o.s.j.s.JdbcTransactionManager - Switching JDBC Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root@localhost, MySQL Connector/J] to manual commit
DEBUG o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
DEBUG o.s.j.s.JdbcTransactionManager - Initiating transaction rollback
DEBUG o.s.j.s.JdbcTransactionManager - Rolling back JDBC transaction on Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root@localhost, MySQL Connector/J]
DEBUG o.s.j.s.JdbcTransactionManager - Releasing JDBC Connection [1091371323, URL=jdbc:mysql://localhost:3306/table?allowMultiQueries=true, UserName=root@localhost, MySQL Connector/J] after transaction
Looking at these logs.. spring is actually telling me it 'is' rolling back the transaction.. but nothing gets changed in the DB ?
Something odd that I just noticed is that right after dao.save(objectA)
I can already see the name being changed in the DB.. so somehow MyBatis is auto committing and doesn't look like it is using the same connection has the one I see in the logs
This is how I setup the Databasource and configure MyBatis
(I have 2 datasource this is why I'm using this instead of just using application.properties properties.)
@Bean(name = MYBATIS_DATASOURCE_ONE, destroyMethod = "")
public DataSource dataSource(....) throws SQLException {
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver(driverclassname);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
[...]
return dataSource;
}
@Bean(name = A_SESSION_FACTORY, destroyMethod = "")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier(MYBATIS_DATASOURCE_A) DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/*.xml"));
final SqlSessionFactory sqlSessionFactory = factoryBean.getObject();
return sqlSessionFactory;
}
CodePudding user response:
You latest update made it clearer... What is happening I think is that Spring is not using your manually defined DataSource when instantiating the DataSourceTransactionManager
. It is probably using a BasicDataSource
or something similar (one that has been created using the properties from your application.properties
Add this to your configuration and the transaction should start working
@Bean
public DataSourceTransactionManager transactionManager(@Qualifier(MYBATIS_DATASOURCE_A) DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
Normally, you wouldn't need to manually define this Bean because Spring would automatically do it for you. But since you are using your own DataSource you need to do it.
If you want to verify this. Don't declare this bean and put a breakpoint in the DataSourceTransactionManager
Constructor.
You should notice that the DataSource is not the same instance as the one you have declared yourself.