4

Given this example code:

public class MyServiceImpl implements MyService {
    @Transactional
    public void myTransactionalMethod() {
        List<Item> itemList = itemService.findItems();
        for (Item anItem : itemList) {
            try {
                processItem(anItem);
            catch (Exception e) {
                // dont rollback here 
                // rollback just one item
            }     
        }

    }

    @Transactional
    public void processItem(Item anItem) { 
        anItem.setSomething(new Something);
        anItem.applyBehaviour();
        itemService.save(anItem);
    }
}

Here is what I want to achieve:

  1. Only processItem(anItem); should rollback if exception occurs inside it.
  2. If exception occurs, myTransactionalMethod should continue, that means the for-each should end.
  3. If exception occurs inside myTransactionalMethod but not in processItem(anItem), myTransactionalMethod should rollback completely.

Is there a solution that doesn't involve managing transactions manually (without annotations)?.

Edit: I was thinking of using @Transactional(PROPAGATION=REQUIRES_NEW), don't know if it will work within the same bean though.

Xstian
  • 8,184
  • 10
  • 42
  • 72
dantebarba
  • 1,396
  • 1
  • 12
  • 22

2 Answers2

7

This is a common misunderstanding. Spring Transactions are implemented through proxies. Proxies are a wrapper around your class. You are accessing the processItem method from the same class, i.e. you don't go through the proxy, so you don't get any transactions. I explained the mechanism in this answer some years ago.

Solution: you need two separate Spring beans if you want nested transactions, both of them must be proxied by @Transactional.

Community
  • 1
  • 1
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
0

It looks like a case for NESTED transaction. NESTED transaction starts a subtransaction with in the outer transaction with savepoint, allowing it rollback to that savepoint. Since it is a nested transactions they committed at the end of outer transation.

    public class MyServiceImpl implements MyService {
    @Transactional
    public void myTransactionalMethod() {
        List<Item> itemList = itemService.findItems();
        for (Item anItem : itemList) {
            try {
               // If you want to call this method directly configure your transaction use to aspectJ for transaction handling or refactor the code. Refer - [http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo][1]
                processItem(anItem);
            catch (Exception e) {
                // dont rollback here 
                // rollback just one item
            }     
        }

    }

    @Transactional(PROPAGATION = PROPAGATION.NESTED)
    // Throw some runtime exception to rollback or some checkedException with rollbackFor attribute set in the above annotation
    public void processItem(Item anItem) { 
        anItem.setSomething(new Something);
        anItem.applyBehaviour();
        itemService.save(anItem);
    }
  }

Note that, I have not yet tried this below code see if that helps. You might have to tweak it, if needed. In fact I would love to give this code a try myself sometime soon.

  • I agree with the general idea of the solution above, but the code would not work. See [this](http://stackoverflow.com/a/9020408/2504224). In order for the solution to work, you need to move `processItem` to another class – geoand Dec 14 '15 at 14:43
  • @geoand That is true. Either move it another class (that is what I meant by refactor, I should have been more specific) or use aspectJ for transaction handling, as per [link](http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo), unless I interpreted the link wrong. – Madhusudana Reddy Sunnapu Dec 14 '15 at 14:58
  • 1
    That's correct. Usage of `AspectJ` for solving such a simple a case is overkill I would argue – geoand Dec 14 '15 at 15:06
  • Looks like, we can even use AopContext.getCurrentProxy() and call processItem(...) in the for-each loop. But for this we need tell Spring to expose the current proxy. – Madhusudana Reddy Sunnapu Dec 14 '15 at 15:56
  • @MadhusudanaReddySunnapu using AspectJ is not an option at the moment, at least until we dig in a little bit further on the matter, but it's a viable alternative. – dantebarba Dec 14 '15 at 18:26
  • 1
    @dantebarba I agree it is not a simple change so the other options I see are moving processItem to new class or using AopContext.getCurrentProxy() or making MyServiceImpl implement ApplicationContextAware and getting the MyServiceImpl from applicationContext in the for-each loop provided it is a prototype bean. – Madhusudana Reddy Sunnapu Dec 15 '15 at 02:20
  • @MadhusudanaReddySunnapu I like that, looks simpler. – dantebarba Dec 15 '15 at 14:54