8

We have a Spring Transaction rollback issues, where rollback doesn't seems to be working.
Within my service layer method which is annotated with @Transactional I call three different DAOImpl classes to insert 3 records.
The middle insert do a get from a 4th table to populate a description field but this get failed. I expect the first insert to rollback but it doesn't seems to be happening.
Few Points:

  1. The 'Get' method throws a Runtime Exception
  2. We are using org.springframework.jdbc.datasource.DataSourceTransactionManager and MySQL datasource defined in applicationContext.xml. Beans are created in Beans.xml which is imported into ApplicationContext.xml
  3. No @Transactional annotation in DAO layer
  4. We have used <tx:annotation-driven transaction-manager="transactionManager"/> again in applicationContext.xml
  5. We are using Spring 3.1

UPDATE:

Code snippets....

Service Class- This is somthing similar to what I have .... I tested with and without @Autowired. The transaction enable method is called within the service class.

public class CustomerService {

    //@Autowired
    CustomerOrderDAO customerOrderDAOImpl;
    //@Autowired
    CustomerItemDAO customerItemDAOImpl;
    //@Autowired
    CustomerPromotionDAO customerPromotionDAOImpl;
    //@Autowired
    PromotionDAO promotionDAOImpl;

    //other variables


    public CustomerOrder handleIncomingOrders(CustomerOrder customerOrder) {
        try {
            saveOrderDetails(customerOrder);
            .....
            return customerOrder;
        } catch (Exception e) //TO-DO catch proper exception 
        {
            //Send error response
            .......
            return customerOrder;
        }
    }

    @Transactional
    public void saveOrderDetails(CustomerOrder customerOrder) throws Exception {
            customerOrderDAOImpl.create(customerOrder);
            ....
            while (promotionsIterator.hasNext()) {
                customerPromotion.setPromotionName(promotionDAOImpl.getName(customerOrder.getPromotionId));
                customerPromotionDAOImpl.create(customerPromotion);
            }
            ......
            while (customerItemIterator.hasNext()) {
                customerItemDAOImpl.create(customerItem);
            }

    }
}

Any idea? Thanks.

Ish
  • 171
  • 1
  • 2
  • 10
  • 3
    Can you show us some code and the way you calling the Service method? from within the service or externally? or any try catch you written yourself? – rahul maindargi Apr 23 '13 at 10:49
  • do you have a propagation set in the transaction definition – Arun P Johny Apr 23 '13 at 10:57
  • Do you have your connection in `autocommit` mode? – kan Apr 23 '13 at 11:39
  • @rahul maindargi I have added the service class code. Transactional annotation is not used anywhere in my application other than in the above method. So I thought propagation is not relevant. Can you please tell me how to check that. I'll goolge in the meantime :) – Ish Apr 24 '13 at 03:50
  • What is the transaction propagation type? It could happen if it were set to `REQUIRES_NEW` like `propagation=Propagation.REQUIRES_NEW`. – Lion Apr 24 '13 at 03:51
  • @Lion I haven't use any. I do not have multiple transactions defined in the application .... in that case propagation is not applied. isn't it? – Ish Apr 24 '13 at 03:55
  • Right, the default is `REQUIRED`. – Lion Apr 24 '13 at 03:56

2 Answers2

7

The default behaviour of @Transactional is that transactional behaviour is added with a proxy around the object (the CustomerService in your example). From the reference docs (scroll down):

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

In your example, an external call to the handlingIncomingOrders() passes through the proxy and hits the target object (an instance of the CustomerService). However, the subsequent call to saveOrderDetails() is a normal method call inside the target object, thus the transactional behaviour in the proxy is never invoked. However, if the saveOrderDetails() was called from another class, you will find that the transactional behaviour will work as expected.

matsev
  • 32,104
  • 16
  • 121
  • 156
  • Thanks. It worked. Although, I find it bit inconvenient. How can we change the default proxy mode? – Ish Apr 26 '13 at 06:27
  • I think the answer is `"Consider the use of AspectJ mode (see mode attribute in table below) if you expect self-invocations to be wrapped with transactions as well. In this case, there will not be a proxy in the first place; instead, the target class will be weaved (that is, its byte code will be modified) in order to turn @Transactional into runtime behavior on any kind of method.` – Ish Apr 26 '13 at 06:31
  • @Ish I usually make a clear definition of my transaction boundary by using the `@Transactional` annotation on the class level. Moreover I avoid or refactor any (public) method that calls the target object from itself, so that the transactions will be explicit. – matsev Apr 26 '13 at 13:16
2

The solution in your case would be calling saveOrderDetails(customerOrder); as proxyBean.saveOrderDetails(customerOrder); Where proxybean is the Object on whichhandleIncomingOrders` is being called.

If CustomerService is singleton (Defualt scope) it can be as simple as adding below code to the Service class. (adding a self reference as autowired)

//@Autowired
CustomerService customerService; // As this is injected its a proxy

and in the Method use it as

 public CustomerOrder handleIncomingOrders(CustomerOrder customerOrder) {
    try {
        customerService.saveOrderDetails(customerOrder);
        .....
        return customerOrder;
    } catch (Exception e) //TO-DO catch proper exception 
    {
        //Send error response
        .......
        return customerOrder;
    }
  }

If its scope is Prototype the one of possible simple solution will be as follows.

public CustomerOrder handleIncomingOrders(CustomerOrder customerOrder, CustomerService customerService) {
    try {
        customerService.saveOrderDetails(customerOrder);
        .....
        return customerOrder;
    } catch (Exception e) //TO-DO catch proper exception 
    {
        //Send error response
        .......
        return customerOrder;
    }
  }

And where you are calling handleIncomingOrders use changes suggested in below code.

    bean.handleIncomingOrders(customerOrder); //Suppose this is old code 
Change it to 
    bean.handleIncomingOrders(customerOrder, bean);// THough it appears as we are sending reference to `THIS` as parameter whcihc can be unnecessary, in case of `Proxy`while inside your method `this` and `Passed reference` will point to different Obejects. 
rahul maindargi
  • 5,359
  • 2
  • 16
  • 23