In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article is about how Spring Boot integrates MyBatis to achieve optimistic and pessimistic locks. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.
Taking the transfer operation as an example, this paper implements and tests optimistic lock and pessimistic lock.
Deadlock problem
When two accounts An and B transfer money to each other at the same time, the following occurs:
Transaction 1 (transfer from A to B) transaction 2 (transfer from B to A) T1Lock ALock BT2Lock B (because transaction 2 has already Lock A, wait) Lock A (because transaction 1 has already Lock B, wait)
Because both transactions are waiting for the other to release the lock, a deadlock arises, and the solution: lock according to the size of the primary key, always lock the row with the smaller or larger primary key first.
Create a data table and insert data (MySQL) create table account (id int auto_increment primary key, deposit decimal (10,2) default 1000 not null, version int default 0 not null); INSERT INTO vault.account (id, deposit, version) VALUES (1, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (2, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (3, 1000, 0) INSERT INTO vault.account (id, deposit, version) VALUES (4, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (5, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (6, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (7, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (8, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (9, 1000, 0) INSERT INTO vault.account (id, deposit, version) VALUES (10, 1000, 0); Mapper file
Pessimistic locks use select... For update, optimistic locks use the version field.
Select * from account where id = # {id} update account set deposit=# {deposit} Version = version + 1 where id = # {id} and version = # {version} select * from account where id = # {id} for update update account set deposit=# {deposit} where id = # {id} select sum (deposit) from account Mapper interface @ Componentpublic interface AccountMapper {Account selectById (int id); Account selectByIdForUpdate (int id); int updateDepositWithVersion (Account account); void updateDeposit (Account account); BigDecimal getTotalDeposit ();} Account POJO@Datapublic class Account {private int id; private BigDecimal deposit; private int version;} AccountService
There is a custom annotation @ Retry on the transferOptimistic method, which is used to implement optimistic locks and retry after failure.
@ Slf4j@Servicepublic class AccountService {public enum Result {SUCCESS, DEPOSIT_NOT_ENOUGH, FAILED,} @ Resource private AccountMapper accountMapper; private BiPredicate isDepositEnough = (deposit, value)-> deposit.compareTo (value) > 0 / * transfer operation, pessimistic lock * * @ param fromId withholding account * @ param toId collection account * @ param value amount * / @ Transactional (isolation = Isolation.READ_COMMITTED) public Result transferPessimistic (int fromId, int toId, BigDecimal value) {Account from, to Try {/ / first lock the line with larger id to avoid deadlock if (fromId > toId) {from = accountMapper.selectByIdForUpdate (fromId); to = accountMapper.selectByIdForUpdate (toId);} else {to = accountMapper.selectByIdForUpdate (toId); from = accountMapper.selectByIdForUpdate (fromId) }} catch (Exception e) {log.error (e.getMessage ()); TransactionAspectSupport.currentTransactionStatus () .setRollbackOnly (); return Result.FAILED;} if (! isDepositEnough.test (from.getDeposit (), value)) {TransactionAspectSupport.currentTransactionStatus () .setRollbackOnly () Log.info (String.format ("Account% d is not enough.", fromId)); return Result.DEPOSIT_NOT_ENOUGH;} from.setDeposit (from.getDeposit (). Subtract (value)); to.setDeposit (to.getDeposit (). Add (value)); accountMapper.updateDeposit (from); accountMapper.updateDeposit (to); return Result.SUCCESS } / * transfer operation, optimistic lock * @ param fromId deduction account * @ param toId collection account * @ param value amount * / @ Retry @ Transactional (isolation = Isolation.REPEATABLE_READ) public Result transferOptimistic (int fromId, int toId, BigDecimal value) {Account from = accountMapper.selectById (fromId), to = accountMapper.selectById (toId) If (! isDepositEnough.test (from.getDeposit (), value)) {TransactionAspectSupport.currentTransactionStatus (). SetRollbackOnly (); return Result.DEPOSIT_NOT_ENOUGH;} from.setDeposit (from.getDeposit (). Subtract (value)); to.setDeposit (to.getDeposit (). Add (value)); int R1, R2 / / first lock the line with higher id to avoid deadlock if (from.getId () > to.getId ()) {R1 = accountMapper.updateDepositWithVersion (from); R2 = accountMapper.updateDepositWithVersion (to);} else {R2 = accountMapper.updateDepositWithVersion (to); R1 = accountMapper.updateDepositWithVersion (from);} if (R1)
< 1 || r2 < 1) { // 失败,抛出重试异常,执行重试 throw new RetryException("Transfer failed, retry."); } else { return Result.SUCCESS; } }}使用 Spring AOP 实现乐观锁失败后重试自定义注解 Retry@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Retry { int value() default 3; // 重试次数}重试异常 RetryExceptionpublic class RetryException extends RuntimeException { public RetryException(String message) { super(message); }}重试的切面类 tryAgain 方法使用了 @Around 注解(表示环绕通知),可以决定目标方法在何时执行,或者不执行,以及自定义返回结果。这里首先通过 ProceedingJoinPoint.proceed() 方法执行目标方法,如果抛出了重试异常,那么重新执行直到满三次,三次都不成功则回滚并返回 FAILED。 @Slf4j@Aspect@Componentpublic class RetryAspect { @Pointcut("@annotation(com.cloud.demo.annotation.Retry)") public void retryPointcut() { } @Around("retryPointcut() && @annotation(retry)") @Transactional(isolation = Isolation.READ_COMMITTED) public Object tryAgain(ProceedingJoinPoint joinPoint, Retry retry) throws Throwable { int count = 0; do { count++; try { return joinPoint.proceed(); } catch (RetryException e) { if (count >Retry.value () {log.error ("Retry failed!"); TransactionAspectSupport.currentTransactionStatus () .setRollbackOnly (); return AccountService.Result.FAILED;} while (true);}} Unit Test
Using multiple threads to simulate concurrent transfer, after testing, pessimistic locks are all successful except for insufficient account balances, insufficient database connections and wait timeouts; optimistic locks are all successful even if they are retried, with an average of 500 successful threads.
Therefore, for operations that write more and read less, pessimistic locks are used, and optimistic locks can be used for operations that read more and write less.
The complete code can be found in Github: https://github.com/imcloudfloating/Lock_Demo.
@ Slf4j@SpringBootTest@RunWith (SpringRunner.class) class AccountServiceTest {/ / concurrency private static final int COUNT = 500; @ Resource AccountMapper accountMapper; @ Resource AccountService accountService; private CountDownLatch latch = new CountDownLatch (COUNT); private List transferThreads = new ArrayList (); private List transferAccounts = new ArrayList (); @ BeforeEach void setUp () {Random random = new Random (currentTimeMillis ()); transferThreads.clear (); transferAccounts.clear () For (int I = 0; I < COUNT; iTunes +) {int from = random.nextInt (10) + 1; int to; do {to = random.nextInt (10) + 1;} while (from = = to); transferAccounts.add (new Pair (from, to)) }} / * Test pessimistic lock * / @ Test void transferByPessimisticLock () throws Throwable {for (int I = 0; I < COUNT; iTunes +) {transferThreads.add (new Transfer (I, true));} for (Thread t: transferThreads) {t.start ();} latch.await () Assertions.assertEquals (accountMapper.getTotalDeposit (), BigDecimal.valueOf (10000) .setScale (2, RoundingMode.HALF_UP));} / * * Test optimistic lock * / @ Test void transferByOptimisticLock () throws Throwable {for (int I = 0; I < COUNT; iTunes +) {transferThreads.add (new Transfer (I, false)) } for (Thread t: transferThreads) {t.start ();} latch.await (); Assertions.assertEquals (accountMapper.getTotalDeposit (), BigDecimal.valueOf (10000) .setScale (2, RoundingMode.HALF_UP));} / * * transfer thread * / class Transfer extends Thread {int index; boolean isPessimistic Transfer (int I, boolean b) {index = I; isPessimistic = b;} @ Override public void run () {BigDecimal value = BigDecimal.valueOf (new Random (currentTimeMillis ()). NextFloat () * 100). SetScale (2, RoundingMode.HALF_UP); AccountService.Result result = AccountService.Result.FAILED Int fromId = transferAccounts.get (index). GetKey (), toId = transferAccounts.get (index). GetValue (); try {if (isPessimistic) {result = accountService.transferPessimistic (fromId, toId, value);} else {result = accountService.transferOptimistic (fromId, toId, value) } catch (Exception e) {log.error (e.getMessage ());} finally {if (result = = AccountService.Result.SUCCESS) {log.info (String.format ("Transfer% f from% d to% d success", value, fromId, toId)) } latch.countDown ();} MySQL configuration innodb_rollback_on_timeout='ON'max_connections=1000innodb_lock_wait_timeout=500 Thank you for reading! This is the end of the article on "how Spring Boot integrates MyBatis to achieve optimistic lock and pessimistic lock". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, you can share it out for more people to see!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.