0

I have a transactional class in my project with following 2 methods:

@Repository(value = "usersDao")
@Transactional(propagation = Propagation.REQUIRED)
public class UsersDaoImpl implements UsersDao {

    @Autowired
    SessionFactory sessionFactory;

    /*  some methods here...  */

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW,readOnly = false,rollbackFor = {Exception.class})
    public void pay(Users payer, int money) throws Exception {
        if( payer.getMoney() < money ) {
            throw new Exception("");
        }
        payer.setMoney(payer.getMoney()-money);
        this.sessionFactory.getCurrentSession().update(payer);
    }

    @Override
    @Transactional(readOnly = false,rollbackFor = {Exception.class})
    public void makeTransfer(Users from, Users to, int money) throws Exception {
        System.out.println("Attempting to make a transfer from " + from.getName() + " to " + to.getName() + "... sending "+ money +"$");

        to.setMoney(to.getMoney()+money);

        if( from.getMoney() < 10 ) {
            throw new Exception("");
        }
        pay(from, 10);

        if( from.getMoney() < money ) {
            throw new Exception("");
        }
        from.setMoney(from.getMoney()-money);

        this.sessionFactory.getCurrentSession().update(from);
        this.sessionFactory.getCurrentSession().update(to);
    }
}

The assumption is that when somebody's making a transfer, they must pay 10$ tax. Let's say there are 2 users who have 100$ both and I want to make a transfer (User1->User2) of 95$. First in makeTransfer I check if User1 is able to pay a tax. He is so I'm moving forward and checking if he's got 95$ left for transfer. He doesn't so the transaction is rolled back. The problem is, in the end they both have 100$. Why? For method pay I set Propagation.REQUIRES_NEW, which means it executes in a separate transaction. So why is it also rolled back? The tax payment should be actually save into a database and only the transfer should be rolled back. The whole point of doing this for me is understanding propagations. I understand them teoretically but can't manage to do some real example of it(How propagation change affects my project). If this example is misunderstanding I'd love to see another one.

just hi
  • 45
  • 6
  • Works as expected. As explained http://stackoverflow.com/questions/15767914/strange-behaviour-with-transactionalpropagation-propagation-requires-new?rq=1. – M. Deinum Apr 23 '15 at 14:01

2 Answers2

0

What M. Deinum said.

Furthermore, according to the Spring documentation:

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.

To use aspectj, write

<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj"/>

instead of

<tx:annotation-driven transaction-manager="transactionManager" />

Source:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

couscousman
  • 94
  • 1
  • 6
  • 1
    To use AspectJ mode you also have to either use load or compile time weaving of your classes. Just switching to AspectJ mode isn't going to work (if you don't combine it with weaving you have no transactions). – M. Deinum Apr 23 '15 at 18:31
0

I solved my problem. Thanks guys for pointing out allowing AspectJ but that wasn't the end. I did deeper research and it turned out that in @Transactional Spring Beans there's an aspect proxy, which is responsible(as far as get it) for operating on transactions(creating, rolling back etc.) for @Transactional methods. But it fails on self reference, because it bypasses proxy, so no new transactions are created. A simple way I used to get this working is calling function pay(..) not refering to this, but to a bean from the spring container, like this:

UsersDaoImpl self = (UsersDaoImpl)this.context.getBean("usersDao");
self.pay(from, 10);

Now, as it refers to the bean, it goes through the proxy so it creates a new transaction.


Just for learning issues I used those two lines to detect whether the two transactions are the same objects or not:

TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
System.out.println("Current transaction: " + status.toString());
just hi
  • 45
  • 6
  • If it doesn't work with AspectJ you aren't using load or compile time weaving but still are using proxies. It will only work if you also enable one of the earlier mentioned methods. As long as you use proxies you will run into this issue. – M. Deinum Apr 24 '15 at 13:55
  • I will learn about AspectJ later. For now I learned to handle proxies and it helped me to understand how they work. – just hi Apr 24 '15 at 14:02