Transaction Management with Spring Transactions

Spring Transactions library is useful for transaction management in your Spring-Hibernate application. A transaction is a single unit of work, in other words – a group of database interactions that should happen together. If any one of these ‘database interactions’ fail then the changes of the previous interactions in the set should be undone.

Transactions are usually (depending on granularity) declared at the service layer of your application. Service layer is the logical place where your transaction should start and a typical service layer method would call 1 or more DAO layer methods.

Spring @Transantional annotation allows the developer to declare a transaction. The typical usage of @Transactional annotation would look like below:

@Service
public class ApplicationServiceImpl implements ApplicationService {

    private static final Logger logger = Logger.getLogger(ApplicationServiceImpl.class);

    @Autowired private ApplicationDao applicationDao;

    @Autowired private QueueDao queueDao;

    @Autowired private UserDao userDao;

    @Override
    @Transactional
    public void saveApplication(Application app) throws Exception {
        logger.info("saving app");
        applicationDao.saveApplication(app);
        queueDao.addToQueue(app);
        userDao.updateUser(app.getUser());
    }
}

Important to remember while using @Transactional is to define the condition for rollback. It is a common to incorrectly assume that because you’ve used @Transactional, Spring will take care of rolling back the whole transaction if something fails. This is not true. Spring only rolls back the transaction if your method throws an unchecked transaction i.e. Runtime Exception. It is much better practice to use the rollbackFor attribute instead. The rollbackFor attribute tells Spring what other situation (in addition to a runtime exception) to rollback for. In the example below I specify that transaction should be rolled back if any exception is thrown using attribute rollbackFor=Exception.class.


@Service
public class ApplicationServiceImpl implements ApplicationService {
private static final Logger logger = Logger.getLogger(ApplicationServiceImpl.class);
@Autowired private ApplicationDao applicationDao;

    @Autowired private QueueDao queueDao;

    @Autowired private UserDao userDao;

    @Override
    @Transactional(rollbackFor=Exception.class)
    public void saveApplication(Application app) throws Exception {
        logger.info("saving app");
        applicationDao.saveApplication(app);
        queueDao.addToQueue(app);
        userDao.updateUser(app.getUser());
    }
}

In addition to above, one way to ensure that DAO methods are always part of an existing transaction initiated at the service layer is to use propagation = Propagation.MANDATORY attribute in your DAO method. If all your transactions are initiated at the service layer it would be good idea of put this attribute on all your DAO methods. With this annotation in place, whenever a DAO method is called without the caller service starting a transaction, you would get an exception stating that no active transactions existed when method was invoked.

@Repository
public class ApplicationDaoImpl implements ApplicationDao {

    private static final Logger logger = Logger.getLogger(ApplicationDaoImpl.class);

    @Autowired private SessionFactory sessionFactory;

    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void saveApplication(Application app) throws Exception {
        Session session = sessionFactory.getCurrentSession();
        session.save(app);
    }
}