Home > Software engineering >  Spring Boot not rolling back transaction on exception
Spring Boot not rolling back transaction on exception

Time:11-11

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

MyBatis documentation clearly specifies that for Transaction to work the DataSourceTransactionManager needs to have the exact same Datasource the rest of your app is using otherwise it won't work.

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.

  • Related