1

I'm struggling with weird behaviour of method annotated with Propagation.REQUIRES_NEW.

In body of method placeOrder() the last operation is sending sms which could throws runtime exception.

I don't mind that sending sms will throw exception, so I don't want to have rollback of transaction started in placeOrder().

Method sendSms() has propagation REQUIRES_NEW so from what I understand, it should not trigger rollback from suspended transaction, only

@Component
public class OrderService {

    @Autowired
    private OrderDao orderDao;

    @Autowired
    private ProductDao productDao;

    @Autowired
    private WarehouseService warehouseService;

    @Autowired
    private SmsClient smsClient;

    @Autowired
    private UserDao userDao;

    @Transactional
    @Override
    public void placeOrder(PlaceOrderRequest placeOrderRequest) {
        Product product = productDao.getById(placeOrderRequest.getProductId());
        WarehouseItem warehouseItem = warehouseService.getAvailable(product);
        Order order = orderDao.createNew();
        order.setProduct(product);
        order.setUser(user);
        smsClient.sendSms();
    }
}

@Component
public class SmsClient {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendSms() {
        throwEx();
    }

    private void throwEx() throws SmsClientException {
        throw new SmsClientException();
    }
}

Here are logs from TransactionManager:

Creating new transaction with name [ai.optime.springmicroservicetemplate.domain.order.impl.DefaultOrderService.placeOrder]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(1447696365<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@25711903]
Executing DAO method 'getById' from class 'JpaBasedProductDao' with args {1}
Found thread-bound EntityManager [SessionImpl(1447696365<open>)] for JPA transaction
Participating in existing transaction
Executing DAO method 'getAvailable' from class 'JpaBasedWarehouseItemDao' with args {1}
HHH000397: Using ASTQueryTranslatorFactory
Found thread-bound EntityManager [SessionImpl(1447696365<open>)] for JPA transaction
Participating in existing transaction
Executing DAO method 'createNew' from class 'JpaBasedOrderDao' with args {}
Found thread-bound EntityManager [SessionImpl(1447696365<open>)] for JPA transaction
Participating in existing transaction
Executing DAO method 'getById' from class 'JpaBasedUserDao' with args {1}
Found thread-bound EntityManager [SessionImpl(1447696365<open>)] for JPA transaction
Participating in existing transaction
Found thread-bound EntityManager [SessionImpl(1447696365<open>)] for JPA transaction
Suspending current transaction, creating new transaction with name [ai.optime.springmicroservicetemplate.api.sms.SmsClient.sendSms]
Opened new EntityManager [SessionImpl(1649890926<open>)] for JPA transaction
Exposing JPA transaction as JDBC org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@6f98ca1d]
Initiating transaction rollback
Rolling back JPA transaction on EntityManager [SessionImpl(1649890926<open>)]
Closing JPA EntityManager [SessionImpl(1649890926<open>)] after transaction
Resuming suspended transaction after completion of inner transaction
Initiating transaction rollback
Rolling back JPA transaction on EntityManager [SessionImpl(1447696365<open>)]
Closing JPA EntityManager [SessionImpl(1447696365<open>)] after transaction
kamil
  • 39
  • 1
  • 6

1 Answers1

1

Your understanding is wrong. You're not catching the exception thrown by smsClient.sendSms(), so it's thrown from placeOrder(), so the transaction started for this method is rollbacked.

Besides, making sendSms() transactional doesn't make much sense, since sending an SMS most probably doesn't interact with any transactional resource.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • I intentionally do not catch `SmsClientException()`, because It should be caught by exception handler. I do not want to rollback changes made by `placeOrder()` before execution of `sendSms()` so I annotate `sendSms()` with `@Transactional(propagation = Propagation.REQUIRES_NEW)`, that in my understanding, should create a new transaction and all exceptions thrown in this method should not affect transaction started in `placeOrder()`. – kamil Nov 15 '19 at 08:40
  • Maybe I add that I was inspired by this post - https://stackoverflow.com/a/16166576/10848494 – kamil Nov 15 '19 at 08:45
  • The difference is, that in the post you reference, the exception is caught and handled. In your code it is thrown back up through the calls stack and it is of type `RuntimeException` rather than a checked exception then it is automatically triggering a rollback in the first transaction. – Alan Hay Nov 15 '19 at 09:10
  • Just as Alan said. Again, if a transactional method throws a runtime exception (as it does in your example, since you don't catch it), then the transaction will be rollbacked. – JB Nizet Nov 15 '19 at 10:10
  • Ok, now I understand that clearly. Thank's for help, really appreciate it. – kamil Nov 15 '19 at 14:03