Transaction propagation

  This is a classical problem. I've met this kind of problems when i trying to solve a one person-one order requirement. In order to solve this, i watched a video that really inspired me.

  Just pretend that we have these two methods:

@Transactional
public void secondKill(Long couponId){
    getCouponById(coupon);
    deductStock();
    save(order);
}


@Transactional
public boolean deductStock(){
    // deduct stock logic...
}

  A classical situation is like the example above. You have a method which declared the Transactional annotation outside there, but this method also invoke another method which have @Transactional annotation. In this cathe transaction will become invalid.

  Why does this happens? To figure out why we have to understand its underlying principle for Mysql. When you run a code like above, mysql will create a sql like this:

BEGIN:
    SELECT * FROM tb_coupion where id = ?
    BEGIN:
        UPDATE tb_coupon
        SET stock = stock - 1 
    COMMIT
    INSERT INTO tb_order
COMMIT

  But we have to know, Mysql do not support Nested Transactions in a single connection. I'm not very sure about other databases like, but Mysql really don't support it. Which means if you begin a transaction, and you start another transaction inside it, Mysql will commit the external transaction as soon as you start the interal transaction. And after the interal transaction commited, the following code in the external will be executed without a transaction.

  Now you know why this problem happens, but how to solve this? Well, this depends on your own business requirement, for example, if you want your whole business line is atomic, then you can integrate transaction inside into outside by simply remove the begin and commit inside, just like this:

BEGIN:
    SELECT * FROM tb_coupion where id = ?

        UPDATE tb_coupon
        SET stock = stock - 1 

    INSERT INTO tb_order
COMMIT

And Spring will take care of the reminder.

  But what if you don't want some exceptions happen in the internal transaction will affect the external transaction and let it rollback? In this instance, you should use a method called pending.

  A pending method allows you to get another connection before internal transaction begins by pending it. Which means now you have two connections that will not bother each other. Once ended the internal transaction, the external transaction will be notified. Of course we can use threadlocal to bind the thread with connection, that will let you to save the external connection somewhere, that's why it's called pending, and start a new one. Show you in the code be like this:

BEGIN:
    SELECT * FROM tb_coupion where id = ?
    -- save connection and pending
    -- new a connection
    BEGIN:
        UPDATE tb_coupon
        SET stock = stock - 1 
    COMMIT
    -- switch back
    INSERT INTO tb_order
COMMIT

  Another solution is nested transaction. We already know that nested transactions are not allowed in Mysql, but maybe we can simulate that using SavePoint. You can save the current process to a point a, and start internal sql, then you can rollback to point a as soon as internal sql commited. Just like this:

BEGIN:
    SELECT * FROM tb_coupion where id = ?
    SAVEPOINT a

        UPDATE tb_coupon
        SET stock = stock - 1 
        -- rollback to a when exception happens
    ROLLBACK to a
    INSERT INTO tb_order
COMMIT

  As you see, the code inside are actually not a transaction, it's just simply rollback to a while code inside is done.

  In this way I'm sure that when you come back to see the seven transaction propagations, you will find it's really easy. I will demonstrate them now.

  1. PROPAGATION_REQUIRED: represents that the current method must be surrounded by a transaction anyway. If there is not one, spring will create one for us. This is common used in almost all kinds of update/insert/delete behaviors.
  2. PROPAGATION_SUPPORTS: literally, the transaction inside will support the transaction outside, if there is a transaction outside, then i will be integrated. Or if there is no transaction outside, then i will be executed without a transaction. This is common used in almost all kinds of read-only behaviors.
  3. PROPAGATION_MANDATORY: code inside must be propagate into a environment with a transaction. If there is no transaction outside, an exception will be throwed. Rarely uesd.
  4. PROPAGATION_REQUIRED_NEW: must executed in a new transaction. If there is a transaction outside, it will be pended. If not, create a new one.
  5. PROPAGATION_NOT_SUPPORTS: this method should not run in a transaction. If there is one outside, then it will be pended and run without transaction.
  6. PROPAGATION_NEVER: this method must not run in an transactional environment, if there is one, throw exception,
  7. PROPAGATION_NESTED: run with a nested transaction.

  Just use them rational in you project, hope this article is helpful for you.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值