Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Transaction Doesn't Rollback

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.

like image 241
Ish Avatar asked Apr 23 '13 10:04

Ish


2 Answers

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.

like image 109
matsev Avatar answered Oct 23 '22 12:10

matsev


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. 
like image 45
rahul maindargi Avatar answered Oct 23 '22 11:10

rahul maindargi