Nested Transaction Implementation
There are two classes OrderService and OrderRepository, OrderRepository will be called inside of OrderService. OrderRepository inserts one record of order into the database.
My Purpose: When an exception happens in OrderRepository or OrderService, the transaction will rollback and the new order will not be inserted into the database.
1. The child has an exception
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Transactional( propagation = Propagation.REQUIRED, rollbackFor = Exception.class ) @Service public class OrderRepositoryImpl implements OrderRepository { @Resource( name = "daoSupport" ) private DaoSupport dao; @Override public void save( Order order ) { dao.save( "OrderMapper.save", order ); throw new RuntimeException(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Transactional( propagation = Propagation.REQUIRED, rollbackFor = Exception.class ) @Service public class OrderServiceImpl implements OrderService { @Resource private OrderRepository orderRepository; @Override public void add( Order order ) { orderRepository.save( order ); } } |
1 2 3 4 5 6 7 |
@Test public void addOrderTestRollback() { Order order = new Order(); order.setId( 1); orderRepository.save( order ); } |
The test passed, when the child OrderRepository gets an exception, the order record insert will rollback.
2. The parent has an exception
2-1. Try 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Transactional( propagation = Propagation.REQUIRED, rollbackFor = Exception.class ) @Service public class OrderRepositoryImpl implements OrderRepository { @Resource( name = "daoSupport" ) private DaoSupport dao; @Override public void add( Order order ) { dao.save( "OrderMapper.add", order ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Transactional( propagation = Propagation.REQUIRED, rollbackFor = Exception.class ) @Service public class OrderServiceImpl implements OrderService { @Resource private OrderRepository orderRepository; @Override public void add( Order order ) { orderRepository.add( order ); throw new RuntimeException(); } } |
1 2 3 4 5 6 7 |
@Test public void addOrderTestRollback() { Order order = new Order(); order.setId( 1); orderRepository.add( order ); } |
The test failed, when the parent OrderService gets an exception, the rollback does not work. The order was inserted into the database.
2-2. Try 2
Change the propagation of transaction of both classes from Propagation.REQUIRED to Propagation.NESTED.
PROPAGATION_REQUIRED behavior means that the same transaction will be used if there is an already opened transaction in the current bean method execution context. If there is no existing transaction the Spring container will create a new one. If multiple methods configured as REQUIRED behavior are called in a nested way they will be assigned distinct logical transactions but they will all share the same physical transaction. In short this means that if an inner method causes a transaction to rollback, the outer method will fail to commit and will also rollback the transaction.
PROPAGATION_NESTED on the other hand starts a “nested” transaction, which is a true subtransaction of the existing one. What will happen is that a savepoint will be taken at the start of the nested transaction. Íf the nested transaction fails, we will roll back to that savepoint. The nested transaction is part of of the outer transaction, so it will only be committed at the end of of the outer transaction. …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Transactional( propagation = Propagation.NESTED, rollbackFor = Exception.class ) @Service public class OrderRepositoryImpl implements OrderRepository { @Resource( name = "daoSupport" ) private DaoSupport dao; @Override public void add( Order order ) { dao.save( "OrderMapper.add", order ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Transactional( propagation = Propagation.NESTED, rollbackFor = Exception.class ) @Service public class OrderServiceImpl implements OrderService { @Resource private OrderRepository orderRepository; @Override public void add( Order order ) { orderRepository.add( order ); throw new RuntimeException(); } } |
1 2 3 4 5 6 7 |
@Test public void addOrderTestRollback() { Order order = new Order(); order.setId( 1); orderRepository.add( order ); } |
The test still failed, when the parent OrderService gets an exception, the rollback does not work. The order was inserted into the database.
2-3. Try 3
Use the transaction manually.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Transactional( propagation = Propagation.NESTED, rollbackFor = Exception.class ) @Service public class OrderRepositoryImpl implements OrderRepository { @Resource( name = "daoSupport" ) private DaoSupport dao; @Override public void add( Order order ) { dao.save( "OrderMapper.save", order ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Transactional( propagation = Propagation.NESTED, rollbackFor = Exception.class ) @Service public class OrderServiceImpl implements OrderService { @Resource private OrderRepository orderRepository; @Override public void add( Order order ) { orderRepository.add( order ); throw new RuntimeException(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Resource private DataSourceTransactionManager transactionManager; @Test public void addOrderTestRollback() { DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior( TransactionDefinition.PROPAGATION_REQUIRED ); TransactionStatus status = transactionManager.getTransaction( def ); try { Order order = new Order(); order.setId( 1); orderService.add( order ); transactionManager.commit( status ); } catch (Exception e) { e.printStackTrace(); transactionManager.rollback( status ); } } |
The test passed, when the parent OrderService gets an exception, the rollback works. The order was not inserted into the database.
Reference: