Infinite recursion caused by MyBatis level-1 cache and mybatis level-1 Cache

Source: Internet
Author: User

Infinite recursion caused by MyBatis level-1 cache and mybatis level-1 Cache

Introduction:

Recently, I participated in a special offer collection activity in the project. When multiple users receive the same offer, I used database lock to control concurrency. The initial assumption was: if multiple people receive a token at the same time, the first person who arrives will receive the token successfully, and other people will continue to find whether there are any remaining tokens. If yes, continue to receive the token; otherwise, the receipt will fail. In implementation, I first used a recursive method to search for sequences. In actual tests, we found infinite recursion, only through degug and materials can we find that this is caused by the primary cache of mybatis. We will share with you the following questions.

1. Knowledge reserve

Brief Introduction:

Mybatis

Level-1 cache: enabled by default. The cache is valid in the current session. After the sqlSession commit (), close (), and merge AchE () operations are executed, the cache is cleared.

Level-2 Cache: It must be manually enabled and global cache is related to mapper namespace.

See http://www.mamicode.com/info-detail-890951.html for details

2. Sample Code

The following is an auxiliary method for getting the discount coupon-randomly selects a discount code and calls the public method of this auxiliary method to start the transaction. During the actual test, it is found that infinite recursion occurs when the database has only one coupon and multiple users receive it at the same time. The Code is as follows:

1/** 2 * randomly select a promotional code 3*4 * @ param codePrefix 5 * prefix 6 * @ return promotional Code 9 */10 private String randExtractOneTicketCode (String mobile, string codePrefix) {11 List <String> notExchangeCodeList = yzTicketCodeDaoExt. getTicketCodeList (codePrefix, 12 MobileServiceConstants. TICKET_CODE_STATUS_NOT_EXCHANGE); 13 logger.info ("getting discount coupons >>> available discount coupons {}", CollectionUtils. size (notExchangeCodeList); 14 if (CollectionUtils. I SEmpty (notExchangeCodeList) {15 logger. warn ("get discount >>> discount coupon {} has been received", codePrefix); 16 throw new YzRuntimeException (MobileServiceConstants. TICKET_NOT_REMAINDER); 17} 18 19 int randomIndex = random. nextInt (notExchangeCodeList. size (); // Random Index 20 String ticketCode = notExchangeCodeList. get (randomIndex); // The randomly selected discount code 21 YzTicketCode ticketCodeObj = yzTicketCodeDaoExt. getByCode (ticketCode); 22 if (ticketCodeObj = Null23 | ticketCodeObj. getStatus ()! = MobileServiceConstants. TICKET_CODE_STATUS_NOT_EXCHANGE) {24 // If the discount coupon has been used 25 logger.info ("get discount coupon >>> discount coupon code {} does not exist or is used", ticketCode ); 26 return randExtractOneTicketCode (String mobile, String codePrefix); // recursive search 27} 28/* 29 * update discount code status 30 */31 ticketCodeObj. setExchangeTime (Calendar. getInstance (). getTime (); 32 ticketCodeObj. setStatus (MobileServiceConstants. TICKET_CODE_STATUS_HAD_EXCHANGED); 33 ticketCodeObj. setMobile (mobile); 34 int updateCnt = yzTicketCodeDaoExt. update4Receive (ticketCodeObj); 35 if (updateCnt <= 0) {36 // The optimistic lock does not affect the row, indicating that the update failed, it may be that this token does not exist or 37 logger.info has been used ("get discount coupon >>> discount coupon code {} does not exist or has been used", ticketCode); 38 return randExtractOneTicketCode (String mobile, string codePrefix); // recursive search 39}; 40 return ticketCode; 41}

Debug found that the query results executed in line 1 were cached by mybatis, so a token can be obtained every time, but in fact this token has already been received by other users, resulting in infinite recursion.

3. Solution

1) for a programmatic transaction, obtain the sqlSession through transactionManager, and then clear the first-level cache through the commit AchE () method of the sqlSession.

2) Because Spring declarative transactions are used in the project, and the concurrency is not high, considering the complexity reduction, the system prompts the user to be busy.

/*** Randomly select a coupon code ** @ param codePrefix * prefix * @ return coupon code * @ throws YzRuntimeException * if no coupon is available */private String randExtractOneTicketCode (String mobile, string codePrefix) {List <String> notExchangeCodeList = yzTicketCodeDaoExt. getTicketCodeList (codePrefix, MobileServiceConstants. TICKET_CODE_STATUS_NOT_EXCHANGE); logger.info ("getting discount coupons >>> available discount coupons {}", CollectionUtils. size (notExchangeCodeList); if (CollectionUtils. isEmpty (notExchangeCodeList) {logger. warn ("Get Discount offer >>> Discount offer {} has been received", codePrefix); throw new YzRuntimeException (MobileServiceConstants. TICKET_NOT_REMAINDER);} int randomIndex = random. nextInt (notExchangeCodeList. size (); // Random Index String ticketCode = notExchangeCodeList. get (randomIndex); // The randomly selected discount code YzTicketCode ticketCodeObj = yzTicketCodeDaoExt. getByCode (ticketCode); if (ticketCodeObj = Null | ticketCodeObj. getStatus ()! = MobileServiceConstants. TICKET_CODE_STATUS_NOT_EXCHANGE) {// If the discount coupon has been used logger.info ("get discount coupon >>> discount coupon code {} does not exist or is used", ticketCode); throw new YzRuntimeException (MobileServiceConstants. TICKET_SYSTEM_BUSY);}/** update discount code status */ticketCodeObj. setExchangeTime (Calendar. getInstance (). getTime (); ticketCodeObj. setStatus (MobileServiceConstants. TICKET_CODE_STATUS_HAD_EXCHANGED); ticketCodeObj. setMobile (mobile); int updateCnt = yzTicketCodeDaoExt. update4Receive (ticketCodeObj); if (updateCnt <= 0) {// The optimistic lock does not affect the row, indicating that the update failed, it may be that this token does not exist or logger.info has been used ("get discount coupon >>> discount coupon code {} does not exist or has been used", ticketCode); throw new YzRuntimeException (MobileServiceConstants. TICKET_SYSTEM_BUSY) ;}; return ticketCode ;}

Summary:

Currently, most projects use clusters. it is not suitable to use the concurrency mechanism provided by java to control concurrency. Database locks and Redis operations are commonly used. The above Code uses optimistic locks of databases, compared with the tragic lock, optimistic Locks require the compilation of external algorithms. Wrong external algorithms and abnormal recovery may easily lead to unknown errors. Therefore, careful design and rigorous testing are required.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.