Infinite recursion caused by mybatis level cache
Introduction:
Recently in the project to participate in a coupon collection activities, when multiple users to receive the same coupon, the use of database lock control concurrency, the initial assumption is: if more than one person to receive a coupon, the first person to arrive at the successful, others continue to find out whether there are remaining coupons, if any, continue to collect, Otherwise, the collection fails. In the implementation, I began to use a recursive way to find coupons, the actual test found infinite recursion, through the DEGUG and access to data only found that this is due to the MyBatis cache, the following will be the problem and share the discussion with you.
1. Points of knowledge involved
MyBatis Cache:
First level cache: Default on, sqlsession level cache, active in current session, execute sqlsession commit (), close (), ClearCache () operation clears cache. [1]
Second level cache: Requires manual open, global level caching, related to mapper namespace. [1]
concurrency control mechanism:
Pessimistic lock: It is assumed that concurrency conflicts occur and that all operations that may violate data integrity are masked. [2]
Optimistic Lock: Assume that there will be no concurrency conflicts and only check for violation of data integrity when committing the operation. [2] Optimistic locking does not solve the problem of dirty reading.
Optimistic locking is suitable for low write ratios, where collisions are really rare, which eliminates the overhead of locking and increases the overall throughput of the system. However, if there is frequent conflict, the upper application will continue to retry, which is to reduce the performance, so in this case, pessimistic locking is more appropriate.
2. Code
Here is an auxiliary method for collecting coupons-randomly extracting a coupon code and calling the public method of the helper method to open the transaction (Sqlsession is turned on). The actual testing process found that when there is only one coupon in the database and is collected by multiple users at the same time, there will be infinite recursion. The code is as follows:
1/** 2 * Randomly draw a coupon code 3 * 4 * @param codeprefix 5 * Coupon code prefix 6 * @return Promo code 9 */10 private string Randextractoneticketcode (string mobile, String codeprefix) {One list<string> Notexchangeco Delist = yzticketcodedaoext.getticketcodelist (codeprefix,12 mobileserviceconstants.ticket_code_status_not_ EXCHANGE), Logger.info ("Collect coupons >>> coupons available Quantity {}", Collectionutils.size (notexchangecodelist)), if (Co Llectionutils.isempty (notexchangecodelist)) {Logger.warn ("collect coupons >>> coupons {} has been completed", Codeprefix); 16 throw new Yzruntimeexception (mobileserviceconstants.ticket_not_remainder);}18 int Randomindex = Random.nextint (Notexchangecodelist.size ()); Random index of the String Ticketcode = Notexchangecodelist.get (Randomindex); Randomly selected coupon code: Yzticketcode ticketcodeobj = Yzticketcodedaoext.getbycode (Ticketcode), if (Ticketcodeobj = = Null23 || Ticketcodeobj.getstatus ()! = Mobileserviceconstants.ticket_code_status_not_exchange) {24//If coupon has been used 25 Logger.info ("claim coupon >>> coupon code {} does not exist or has been used", ticketcode); Randextractoneticketcode return (String mobi Le, String codeprefix); Recursive lookup 27}28/*29 * Update promo code status */31 ticketcodeobj.setexchangetime (calendar.getins Tance (). GetTime ()); Ticketcodeobj.setstatus (mobileserviceconstants.ticket_code_status_had_exchanged); 33 Ticketcodeobj.setmobile (MOBILE), updatecnt int = yzticketcodedaoext.update4receive (ticketcodeobj); updatecnt <= 0) {36//optimistic lock, does not affect the line, indicating that the update failed, may be that the voucher does not exist or has been used the PNS Logger.info ("Collect coupon >>> coupon code {} does not exist or has been used ", ticketcode); Randextractoneticketcode return (String Mobile, string codeprefix); Recursive lookup, found here there is a loop recursive};40 return ticketcode;41}
Debug found that the 38th line of recursive recursion, because the results of the 11th row execution of the query was MyBatis cache cache, resulting in each query results are the results of the first query (there is a coupon can be picked up), but in fact this coupon has been picked up by other users, resulting in infinite recursion.
3. Solution
1) Programmatic transactions, get sqlsession through TransactionManager, and then clear the first-level cache through the sqlsession ClearCache () method.
2) Because of the use of spring-declared transactions in the project, and the concurrency is not high, considering the reduction of complexity, choose a simple method, directly prompt the user system busy.
/** * Randomly extract a coupon code * * @param codeprefix * Coupon code prefix * @return Promo code * @throws Yzruntimeexcepti On * If there is no coupon available */private string Randextractoneticketcode (string mobile, string codeprefix) { list<string> notexchangecodelist = yzticketcodedaoext.getticketcodelist (Codeprefix, MobileServiceC Onstants. Ticket_code_status_not_exchange); Logger.info ("Collect coupons >>> coupons available Quantity {}", Collectionutils.size (notexchangecodelist)); if (Collectionutils.isempty (notexchangecodelist)) {Logger.warn ("collect coupons >>> coupons {} has been completed", codeprefix); throw new Yzruntimeexception (Mobileserviceconstants.ticket_not_remainder); } int randomindex = Random.nextint (Notexchangecodelist.size ()); Random index of String ticketcode = Notexchangecodelist.get (Randomindex); A randomly selected coupon code Yzticketcode ticketcodeobj = Yzticketcodedaoext.getbycode (Ticketcode); if (ticketcodeobj = = NULL || Ticketcodeobj.getstatus ()! = Mobileserviceconstants.ticket_code_status_not_exchange) {//If coupon is already in use Logger.info ("claim coupon >>> coupon code {} does not exist or has been used", ticketcode); throw new Yzruntimeexception (MOBILESERVICECONSTANTS.TICKET_SYSTEM_BUSY); }/* * Update Promo 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) {//Optimistic lock, does not affect the line, indicating that the update failed, may be that the voucher does not exist or has been used Logger.info ("Pick up coupon >>> coupon code {} does not exist or has been made Use ", Ticketcode); throw new Yzruntimeexception (MOBILESERVICECONSTANTS.TICKET_SYSTEM_BUSY); }; return ticketcode; }
Summarize:
Now the project mostly uses the way of the cluster, using the concurrency mechanism provided by Java can not control concurrency, commonly used is the database lock and Redis provides concurrency control mechanism, the above code used the database optimistic lock, optimistic lock compared to the tragic lock, the need to write an external algorithm, Incorrect external algorithms and abnormal recovery are prone to unknown errors that require careful design and rigorous testing.
Reference Documentation:
[1]http://www.mamicode.com/info-detail-890951.html
[2] Concurrent Control Http://en.wikipedia.org/wiki/Concurrency_control
Infinite recursion caused by mybatis level cache