網站首頁 編程語言 正文
需求:修改秒殺業務,要求同一個優惠券,一個用戶只能下一單
我們只需要在增加訂單之前,拿用戶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);
}
}
原文鏈接:https://blog.csdn.net/qq_59212867/article/details/128121316
相關推薦
- 2022-08-14 Qt控件點擊消息獲取的方法詳解_C 語言
- 2022-06-16 C語言從猜數字游戲中理解數據結構_C 語言
- 2022-11-07 python中if的基礎用法(if?else和if?not)_python
- 2022-03-28 Python?NumPy實用函數筆記之allclose_python
- 2023-03-26 Android視圖綁定方法深入探究_Android
- 2022-03-16 VS2022?安裝.NET4.5目標包的方法_實用技巧
- 2023-06-04 Django修改端口號與地址的三種方式_python
- 2022-03-19 C語言常量介紹_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支