91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Redis如何實現優惠券限一單限制

發布時間:2022-12-07 09:57:55 來源:億速云 閱讀:100 作者:iii 欄目:開發技術

本篇內容介紹了“Redis如何實現優惠券限一單限制”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

需求:修改秒殺業務,要求同一個優惠券,一個用戶只能下一單

我們只需要在增加訂單之前,拿用戶id和優惠券id判斷訂單是否已經存在,如果存在,說明用戶已經購買。

Redis如何實現優惠券限一單限制

代碼實現:

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如何實現優惠券限一單限制”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

灵山县| 华池县| 东兰县| 万州区| 拉孜县| 荣昌县| 灵璧县| 基隆市| 贵溪市| 北辰区| 澄城县| 绥中县| 辰溪县| 台中市| 额济纳旗| 沾益县| 大名县| 三门县| 天等县| 丰顺县| 新龙县| 昭苏县| 洛川县| 石嘴山市| 特克斯县| 休宁县| 磴口县| 读书| 荥经县| 栾川县| 翼城县| 湟中县| 许昌县| 连南| 东阿县| 区。| 石首市| 宁陕县| 西吉县| 桦川县| 滨州市|