<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
需求:修改秒殺業務,要求同一個優惠券,一個使用者只能下一單
我們只需要在增加訂單之前,拿使用者id和優惠券id判斷訂單是否已經存在,如果存在,說明使用者已經購買。
程式碼實現:
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服務實現類 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.獲取優惠券資訊 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判斷是否已經開始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒殺尚未開始!"); } //3.判斷是否已經結束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒殺已經結束了!"); } //4.判斷庫存是否充足 if (voucher.getStock() < 1) { Result.fail("庫存不充足!"); } //5.扣減庫存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("庫存不充足!"); } Long userId = UserHolder.getUser().getId(); //6.根據優惠券id和使用者id判斷訂單是否已經存在 //如果存在,則返回錯誤資訊 int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); if (count > 0) { return Result.fail("使用者已經購買!"); } //7. 建立訂單 VoucherOrder voucherOrder = new VoucherOrder(); //7.1新增訂單id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //7.2新增使用者id voucherOrder.setUserId(userId); //7.3新增優惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //8.返回訂單id return Result.ok(orderId); } }
但是,還沒完,這種程式碼邏輯,在高並行的情況下還是會出現一個人購買購買多個的情況:
就是同一時間,多個執行緒來查詢資料,都沒有查到訂單,都去建立了訂單(高並行的情況下)
類似超賣問題,所以我們要進行上鎖。
這次就用悲觀鎖。
最簡單的實現方法,就是把從查詢訂單是否存在到儲存訂單返回訂單id這一段程式碼塊進行封裝成一個方法,然後在這個方法上加上synchronized關鍵字和spring事務。
如下:
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服務實現類 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.獲取優惠券資訊 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判斷是否已經開始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒殺尚未開始!"); } //3.判斷是否已經結束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒殺已經結束了!"); } //4.判斷庫存是否充足 if (voucher.getStock() < 1) { Result.fail("庫存不充足!"); } //5.扣減庫存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("庫存不充足!"); } return createVoucherOrder(voucherId); } @Transactional public synchronized Result createVoucherOrder(Long voucherId) { Long userId = UserHolder.getUser().getId(); //6.根據優惠券id和使用者id判斷訂單是否已經存在 //如果存在,則返回錯誤資訊 int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); if (count > 0) { return Result.fail("使用者已經購買!"); } //7. 建立訂單 VoucherOrder voucherOrder = new VoucherOrder(); //7.1新增訂單id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //7.2新增使用者id voucherOrder.setUserId(userId); //7.3新增優惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //8.返回訂單id return Result.ok(orderId); } }
但是,這個方法就是使用了悲觀鎖,鎖的物件是整個類物件,所有使用者公用一把鎖,就會導致序列執行,從而效能大大降低。
我們可以只鎖上使用者id,讓他每個使用者獲得一把鎖。
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服務實現類 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.獲取優惠券資訊 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判斷是否已經開始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒殺尚未開始!"); } //3.判斷是否已經結束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒殺已經結束了!"); } //4.判斷庫存是否充足 if (voucher.getStock() < 1) { Result.fail("庫存不充足!"); } //5.扣減庫存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("庫存不充足!"); } Long userId = UserHolder.getUser().getId(); return createVoucherOrder(voucherId); } @Transactional public Result createVoucherOrder(Long voucherId) { Long userId = UserHolder.getUser().getId(); //6.根據優惠券id和使用者id判斷訂單是否已經存在 synchronized (userId.toString().intern()){ //如果存在,則返回錯誤資訊 int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); if (count > 0) { return Result.fail("使用者已經購買!"); } //7. 建立訂單 VoucherOrder voucherOrder = new VoucherOrder(); //7.1新增訂單id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //7.2新增使用者id voucherOrder.setUserId(userId); //7.3新增優惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //8.返回訂單id return Result.ok(orderId); } } }
這裡鎖上userid時,除了用toString方法轉成字串,還使用intern方法的原因是:
toString方法的底層原理其實是new一個String物件,然後將其變成字串,如果只鎖上了加toString方法的userid,就有可能出現相同的userid,但是toString底層new出來的String物件不同,而多分了鎖。所以使用intern方法來直接判斷常數池中的string值是否一致,值一樣的共用一把鎖,這樣就不會導致多分鎖了。
但是但是,還沒完因為這裡我們是加了鎖和事務,但是因為這個事務時Spring進行管理的,它會在我們程式碼塊結束後才會去執行事務,也就是我們釋放鎖的時候,才會執行事務。這個時候,鎖放開了,就會有其他執行緒進來,就很有可能出現事務提交帶上了其他執行緒。
我們可以這樣進行改進:在本個方法上進行加鎖。
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服務實現類 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.獲取優惠券資訊 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判斷是否已經開始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒殺尚未開始!"); } //3.判斷是否已經結束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒殺已經結束了!"); } //4.判斷庫存是否充足 if (voucher.getStock() < 1) { Result.fail("庫存不充足!"); } //5.扣減庫存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("庫存不充足!"); } Long userId = UserHolder.getUser().getId(); synchronized (userId.toString().intern()){ return createVoucherOrder(voucherId); } } @Transactional public Result createVoucherOrder(Long voucherId) { Long userId = UserHolder.getUser().getId(); //6.根據優惠券id和使用者id判斷訂單是否已經存在 //如果存在,則返回錯誤資訊 int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); if (count > 0) { return Result.fail("使用者已經購買!"); } //7. 建立訂單 VoucherOrder voucherOrder = new VoucherOrder(); //7.1新增訂單id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //7.2新增使用者id voucherOrder.setUserId(userId); //7.3新增優惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //8.返回訂單id return Result.ok(orderId); } }
但是但是但是,還沒完。哈哈
我們只給建立訂單這個方法(createVoucherOrder)加了事務,但是沒給上面判斷條件的方法加上事務,而我們鎖程式碼塊裡執行的方法,其實是this.createVoucherOrder()方法,是沒有加事務的方法呼叫的createVoucherOrder()方法,這個this可不是spring的事務代理物件,這就會導致事務失效。
解決方法就是,我們只需要拿到代理物件,然後通過代理物件呼叫我們這個加了事務的方法,也就是createVoucherOrder()方法。
使用 AopContext.currentProxy();方法來拿到代理物件
溫馨提示 :使用這個方法前要先做兩件事~
1. 記得在設定類似加上@EnableAspectJAutoProxy(exposeProxy = true)註解來暴露這個代理物件
2. 加上依賴:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency>
完整程式碼;:
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.aop.framework.AopContext; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服務實現類 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.獲取優惠券資訊 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判斷是否已經開始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒殺尚未開始!"); } //3.判斷是否已經結束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒殺已經結束了!"); } //4.判斷庫存是否充足 if (voucher.getStock() < 1) { Result.fail("庫存不充足!"); } //5.扣減庫存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("庫存不充足!"); } Long userId = UserHolder.getUser().getId(); synchronized (userId.toString().intern()){ IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy(); return proxy.createVoucherOrder(voucherId); } } @Transactional public Result createVoucherOrder(Long voucherId) { Long userId = UserHolder.getUser().getId(); //6.根據優惠券id和使用者id判斷訂單是否已經存在 //如果存在,則返回錯誤資訊 int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); if (count > 0) { return Result.fail("使用者已經購買!"); } //7. 建立訂單 VoucherOrder voucherOrder = new VoucherOrder(); //7.1新增訂單id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //7.2新增使用者id voucherOrder.setUserId(userId); //7.3新增優惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //8.返回訂單id return Result.ok(orderId); } }
到此這篇關於Redis實現優惠券限一單限制詳解的文章就介紹到這了,更多相關Redis優惠券內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45