a. Retry the operationb. Modify the operation input and retry itc. Provide a crafted operation response without retrying itd. Mark the operation as failed and raise an appropriate error. The caller would need to handle such a case, and the operation can be attempted (if necessary) again later on, possibly after manual investigation into the issue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.function.Supplier; /** * Since spring ignores transaction settings for methods within the same class, we need a separate service * to run isolated transactions which can be called from anywhere simply by supplying the method to execute */ @Service public class TransactionHandlerService { /** * Runs the given method in the same transaction as the caller * * @param supplier the method to execute * @param <T> * @return the result of the invoked method */ @Transactional(propagation = Propagation.REQUIRED) public <T> T runInSameTransaction(Supplier<T> supplier) { return supplier.get(); } /** * Runs the given void method in the same transaction as the caller * * @param supplier the method to execute */ @Transactional(propagation = Propagation.REQUIRED) public void runVoidInSameTransaction(Runnable supplier) { supplier.run(); } /** * Runs the given method in a separate transaction * * @param supplier the method to execute * @param <T> * @return the result of the invoked method */ @Transactional(propagation = Propagation.REQUIRES_NEW) public <T> T runInNewTransaction(Supplier<T> supplier) { return supplier.get(); } /** * Runs the given method in a separate transaction with strict isolation * * @param supplier the method to execute * @param <T> * @return the result of the invoked method */ @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE) public <T> T runInNewTransactionIsolated(Supplier<T> supplier) { return supplier.get(); } } |
2- Synchronization and locks on classes and methods only block execution until the protected section has been completed. This can have undesired effects when mixed with different transactionality settings.
For example, the following parallel execution:
methodA() calls syncMethod() then calls somethingElse()
methodB() calls syncMethod() then calls somethingElse()
What happens:
methodA opens Transaction A enters syncMethod
methodB opens Transaction B waits on syncMethod
methodA exits syncMethod, starts executing somethingElse
methodB enters syncMethod while somethingElse completes and commits Transaction A
methodB exits syncMethod, starts executing somethingElse
methodB completes somethingElse and commits Transaction B
Now the important part is point 4. If the syncMethod requires fresh data from other executions, methodB will NOT see that data when it enters syncMethod since Transaction A has NOT yet been committed.
Synchronization only blocks a thread from entering the protected code block, but as soon as the resource is free, the next waiting thread will enter immediately.
This means that, to allow other methods to read the latest data while in the protected section, any operation done in synchronized method must be executed in a separate transaction, for example using the TransactionHandler helper from the previous point .
No comments:
Post a Comment
With great power comes great responsibility