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.
- 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.
- 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.
- 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.
- 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.
- 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.
- PROPAGATION_NEVER: this method must not run in an transactional environment, if there is one, throw exception,
- PROPAGATION_NESTED: run with a nested transaction.
Just use them rational in you project, hope this article is helpful for you.
7万+

被折叠的 条评论
为什么被折叠?



