I have a Spring-managed service method to manage database inserts. It contains multiple insert statements.
@Transactional
public void insertObservation(Obser
This is defined behaviour. From the docs:
Any
RuntimeException
triggers rollback, and any checked Exception does not.
This is common behaviour across all Spring transaction APIs. By default, if a RuntimeException
is thrown from within the transactional code, the transaction will be rolled back. If a checked exception (i.e. not a RuntimeException
) is thrown, then the transaction will not be rolled back.
The rationale behind this is that RuntimeException
classes are generally taken by Spring to denote unrecoverable error conditions.
This behaviour can be changed from the default, if you wish to do so, but how to do this depends on how you use the Spring API, and how you set up your transaction manager.
Spring makes extensive use of RuntimeExceptions (including using DataAccessExceptions to wrap SQLExceptions or exceptions from ORMs) for cases where there's no recovering from the exception. It assumes you want to use checked exceptions for cases where a layer above the service needs to be notified of something, but you don't want the transaction to be interfered with.
If you're using Spring you might as well make use of its jdbc-wrapping libraries and DataAccessException-translating facility, it will reduce the amount of code you have to maintain and provide more meaningful exceptions. Also having a service layer throwing implementation-specific exceptions is a bad smell. The pre-Spring way was to create implementation-agnostic checked exceptions wrapping implementation-specific exceptions, this resulted in a lot of busy-work and a bloated code base. Spring's way avoids those problems.
If you want to know why Spring chose to make things work this way, it's probably because they use AOP to add the transaction-handling. They can't change the signature of the method they are wrapping, so checked exceptions aren't an option.
For @Transactional
, by default, rollback happens for runtime, unchecked exceptions only. Thus, your checked exception SQLException
does not trigger a rollback of the transaction; the behavior can be configured with the rollbackFor
and noRollbackFor
annotation parameters.
@Transactional(rollbackFor = SQLException.class)
public void insertObservation(ObservationWithData ob) throws SQLException
{
observationDao.insertObservation(ob.getObservation());
// aop pointcut inserted here in unit test
dataDao.insertData(ob.getData());
}