• springboot整合Rocketmq


    【秒杀】

    1.1. 技术选择型

    Ø Springboot

    Ø Redis

    Ø Rocketmq

    Ø Mysql

    Ø MybatisPlus

    1.2. 架构图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3DcJaPnq-1663581493965)(D:/noteLog/typora_files/秒杀/img/wps1.png)]

    1.3. 准备工作-数据库

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkLQCTOU-1663581493965)(D:/noteLog/typora_files/秒杀/img/1663524142863.png)]

    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for goods
    -- ----------------------------
    DROP TABLE IF EXISTS `goods`;
    CREATE TABLE `goods`  (
      `goods_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '商品ID',
      `goods_name` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '商品名称',
      `price` decimal(15, 2) DEFAULT NULL COMMENT '现价',
      `content` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '详细描述',
      `status` int(1) DEFAULT 0 COMMENT '默认是1,表示正常状态, -1表示删除, 0下架',
      `total_stocks` int(11) DEFAULT 0 COMMENT '总库存',
      `create_time` datetime(0) DEFAULT NULL COMMENT '录入时间',
      `update_time` datetime(0) DEFAULT NULL COMMENT '修改时间',
      `spike` int(11) DEFAULT 0 COMMENT '是否参与秒杀1是0否',
      PRIMARY KEY (`goods_id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 95 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '商品' ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of goods
    -- ----------------------------
    INSERT INTO `goods` VALUES (18, 'Apple iPhone XS Max 移动联通电信4G手机 ', 1.01, '
    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    \"\"
    \"\"
    \"\"
    \"\"
    \"\"
    \"\"
    \"\"
    \"\"
    \"\"
    \"\"
    \"\"
    \n
    '
    , 1, 10, '2019-03-29 14:40:00', '2019-06-22 18:28:32', 1); INSERT INTO `goods` VALUES (59, '兰蔻粉水清滢柔肤水400ml 爽肤水女保湿舒缓滋润嫩肤', 420.00, '

    \"\"

    '
    , 1, 10, '2019-04-21 19:15:34', '2019-04-29 14:30:44', 1); INSERT INTO `goods` VALUES (68, '【Dole都乐】菲律宾都乐非转基因木瓜1只 单只约410g', 26.00, '

    \"\"

    '
    , 1, 10, '2019-04-21 21:56:38', '2019-05-22 10:30:37', 0); INSERT INTO `goods` VALUES (69, '阿迪达斯官方 adidas 三叶草 NITE JOGGER 男子经典鞋BD7956', 1199.00, '

    \"\"

    '
    , 1, 10, '2019-04-21 22:10:04', '2019-05-23 20:17:03', 0); INSERT INTO `goods` VALUES (70, '【Dole都乐】比利时Truval啤梨12只 进口水果新鲜梨 单果120g左右', 38.00, '

    \"\"

    '
    , 1, 10, '2019-04-22 16:43:33', '2019-06-22 09:40:24', 0); INSERT INTO `goods` VALUES (71, '旗舰店官网 自拍神器 梵高定制', 6998.00, '

    \"\"

    \n

    \"\"\"\"

    '
    , 1, 10, '2019-04-23 15:43:26', '2019-05-21 11:01:59', 0); -- ---------------------------- -- Table structure for order -- ---------------------------- DROP TABLE IF EXISTS `order`; CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userid` int(11) DEFAULT NULL, `goodsid` int(11) DEFAULT NULL, `createtime` datetime(0) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;

    1.4. 创建项目选择依赖seckill-web(生产者)

    1.4.1. Pom.xml

    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.7.0version>
            <relativePath/> 
        parent>
        <groupId>com.powernodegroupId>
        <artifactId>seckill-webartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>d-seckill-webname>
        <description>d-seckill-webdescription>
        <properties>
            <java.version>1.8java.version>
        properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.apache.rocketmqgroupId>
                <artifactId>rocketmq-spring-boot-starterartifactId>
                <version>2.2.2version>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                plugin>
            plugins>
        build>
    
    project>
    
    

    1.4.2. 修改配置文件

    server:
        port: 8081
        tomcat:
            threads:
                max: 1000 # tomcat
                min-spare: 100 #
            max-connections: 8192  #
            accept-count: 1000 # 
    spring:
        application:
            name: seckill-web # 
        redis:
            host: localhost
            port: 6379
            database: 0
    rocketmq:
        name-server: 47.100.238.122:9876
        producer:
            group: seckill-producer-group
    
    

    1.4.3 编写SeckillController

    package com.powernode.controller;
    
    import com.powernode.utils.BloomFilter;
    import org.apache.rocketmq.client.producer.SendCallback;
    import org.apache.rocketmq.client.producer.SendResult;
    import org.apache.rocketmq.spring.core.RocketMQTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     *
     * 时间: 2022-06-16 10:02
     */
    @RestController
    public class SeckillController {
    
    
        @Autowired
        private BloomFilter bloomFilter;
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        @Autowired
        private RocketMQTemplate rocketMQTemplate;
    
        /**
         * 秒杀的入口
         * 1.一个用户针对一个商品只能买一件
         * 2.库存的判断
         * 3.组装数据放mq
         * 4.return
         *
         * @param goodsId 商品的id
         * @param userId  用户的id 因为没有做登录 做测试把用户id传进来
         * @return
         */
        @GetMapping("doSeckill")
        public String doSeckill(Integer goodsId, Integer userId) {
            // 做去重操作
            // 找到一个唯一的标识
            String seckillId = userId + "-" + goodsId;
            if (bloomFilter.isExist(seckillId)) {
                return "该商品您已经参与过抢购,请参与其他商品(#^.^#)";
            }
            // 存放进去
            bloomFilter.add(seckillId);
            // 判断库存是否足够  这里做一个库存扣减的操作 redis
            // 在操作redis的时候 直接减少  返回值就是减后的结果
            Long count = redisTemplate.opsForValue().decrement("goods_stock:" + goodsId);
            if (count < -3L) {
                // 这个地方判断的临界值 如果是0的话  后面写数据库出现问题 会导致有的请求没有成功处理
                // 页面上的库存数量就会大于0
                // 这里可以多放10%-30%的流量进入抢购
                return "该商品已经被抢完,下次早点来哦(*^▽^*)";
            }
    
            rocketMQTemplate.asyncSend("seckillTopic", seckillId, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.println("放入mq成功");
                }
                @Override
                public void onException(Throwable e) {
                    System.out.println("放入mq失败:" + e.getMessage());
                }
            });
            //
            return "正在拼命抢购中,请稍后再订单中心查看";
        }
    
    }
    
    

    1.4.4 编写utils包下的-BloomFilter

    package com.powernode.utils;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    
    /**
     * 布隆过滤器
     * 空间占用和速度上都比较好
     * 亿万数据量去重
     * 缺点:只能判断重复 取不到具体的值  存储不建议使用
     * 可能存在一定误判  概率很低 千万分之一
     * size的范围大小
     * 散列次数大一点 折中考虑
     */
    @Component // spring在创建bean对象的时候默认调他的无参构造
    public class BloomFilter {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
    
        private Integer size = 2 << 30;
    
        private Integer[] seeds = {7, 11, 13, 17, 19};
    
        public static final Integer HASH_FUN_NUM = 5;
    
        private HashFun[] hashFuns = new HashFun[HASH_FUN_NUM];
    
    
        public BloomFilter() {
            for (int i = 0; i < seeds.length; i++) {
                hashFuns[i] = new HashFun(this.size, seeds[i]);
            }
        }
    
        public void add(String msg) {
            for (HashFun hashFun : hashFuns) {
                int position = hashFun.getPosition(msg);
                // 存起来  存在redis的bitmap  true 就是把这个位置站住
                redisTemplate.opsForValue().setBit("seckill-bloom", position, true);
            }
        }
    
    
        public Boolean isExist(String msg) {
            for (HashFun hashFun : hashFuns) {
                int position = hashFun.getPosition(msg);
                Boolean flag = redisTemplate.opsForValue().getBit("seckill-bloom", position);
                if (!flag) {
                    // 只要有一个没有对应上 就不存在
                    return false;
                }
            }
            return true;
        }
    
    
        public static BloomFilter myBloomFilter() {
            return new BloomFilter();
        }
    
    
        public static void main(String[] args) {
            BloomFilter bloomFilter = new BloomFilter();
            //bloomFilter.add("qihdkasjbhckajcghasd");
            Boolean filterExist = bloomFilter.isExist("qihdkasjbhckajcghase");
            System.out.println(filterExist);
        }
    
    
    }
    
    

    1.4.5 编写utils包下的-HashFun 类

    package com.powernode.utils;
    
    
    public class HashFun {
    
        /**
         * 位图大小
         */
        private Integer size;
    
        /**
         * 种子数 质数
         * 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
         */
        private Integer seed;
    
        public HashFun(Integer size, Integer seed) {
            this.size = size;
            this.seed = seed;
        }
    
        public int hash(String msgId) {
            int h = 0;
            if (msgId == null || msgId.equals("")) {
                return h;
            }
            char[] chars = msgId.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                h = this.seed * h + chars[i];
            }
            return h;
        }
    
        public int getPosition(String msgId) {
            int hash = hash(msgId);
            return hash & (this.size - 1);
        }
    
    
        public static void main(String[] args) {
    
            HashFun hashFun = new HashFun(2 << 30, 23);
    
            //
            //int i = 17 & (16 - 1);
            //System.out.println(i);
    
            String msgId = "qwpduiqoiuerq";
    
            int hash = hashFun.hash(msgId);
            System.out.println(hash);
            int position = hashFun.getPosition(msgId);
            System.out.println(position);
    
        }
    
    
    }
    
    

    1.5. 创建项目选择依赖seckill-service(消费者)

    1.5.0 修改pom文件

    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.7.0version>
            <relativePath/> 
        parent>
        <groupId>com.powernodegroupId>
        <artifactId>seckill-serviceartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>seckill-servicename>
        <description>seckill-servicedescription>
        <properties>
            <java.version>1.8java.version>
        properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>2.2.2version>
            dependency>
    
            <dependency>
                <groupId>org.apache.rocketmqgroupId>
                <artifactId>rocketmq-spring-boot-starterartifactId>
                <version>2.2.2version>
            dependency>
    
            <dependency>
                <groupId>org.redissongroupId>
                <artifactId>redisson-spring-boot-starterartifactId>
                <version>3.13.6version>
            dependency>
    
    
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombokgroupId>
                                <artifactId>lombokartifactId>
                            exclude>
                        excludes>
                    configuration>
                     <configuration>
                        
                        <configurationFile>GeneratorMapper.xmlconfigurationFile>
                        <verbose>trueverbose>
                        <overwrite>trueoverwrite>
                    configuration>
                plugin>
            plugins>
           
        build>
    
    project>
    
    

    1.5.1. 修改yml文件

    server:
        port: 8082
    spring:
        application:
            name: seckill-service
        datasource:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://localhost:3306/seckill?serverTimezone=GMT%2B8
            password: 123456
            username: root
        redis:
            host: localhost
            port: 6379
            database: 0
        task:
            scheduling:
                pool:
                    size: 4  # 如果你自己用定时任务 注意阻塞问题
    mybatis:
        mapper-locations: classpath*:mapper/*.xml
        configuration:
            log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
            map-underscore-to-camel-case: true
    rocketmq:
        name-server: 47.100.238.122:9876
        consumer:
            group: seckill-consumer-group
    
    
    

    1.5.2. 拷贝逆向工程 -GeneratorMapper.xml

    
    DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
    
        
        <classPathEntry location="D:\javaTools\maven_repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>
    
        
        <context id="tables" targetRuntime="MyBatis3">
    
            
            <commentGenerator>
                <property name="suppressAllComments" value="true" />
            commentGenerator>
    
            
            <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                            connectionURL="jdbc:mysql://127.0.0.1:3306/seckill"
                            userId="root"
                            password="root">
            jdbcConnection>
    
            
            <javaModelGenerator targetPackage="com.powernode.model" targetProject="src/main/java">
                <property name="enableSubPackages" value="false" />
                <property name="trimStrings" value="false" />
            javaModelGenerator>
    
            
            <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
                <property name="enableSubPackages" value="false" />
            sqlMapGenerator>
    
            
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.powernode.mapper" targetProject="src/main/java">
                <property name="enableSubPackages" value="false" />
            javaClientGenerator>
    
            
            <table tableName="t_user"
                   domainObjectName="User"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
        context>
    
    generatorConfiguration>
    

    1.5.3. 修改启动类

    package com.powernode;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @SpringBootApplication
    @MapperScan(basePackages = {"com.powernode.mapper"})
    @EnableScheduling // 开启定时任务
    public class ESeckillServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ESeckillServiceApplication.class, args);
        }
    
        @Bean
        public RedissonClient redissonClient() {
            Config config = new Config();
            config.useSingleServer()
                    .setAddress("redis://localhost:6379")
                    .setDatabase(0);
            return Redisson.create(config);
        }
    
    }
    
    

    1.5.4. 创建config包下的-DataSync定时任务类

    同步mysql数据到redis

    package com.powernode.config;
    
    import com.powernode.domain.Goods;
    import com.powernode.mapper.GoodsMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.TimeoutUtils;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    
    import javax.annotation.PostConstruct;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    /**
     *
     * 时间: 2022-06-16 10:41
     * 将mysql的数据同步到redis
     * 提供web去判断库存
     * 搞个定时任务
     */
    @Component
    public class DataSync {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        @Autowired
        private GoodsMapper goodsMapper;
    
        /**
         * 一般在开发中 周期性执行代码 可以使用这个定时任务
         */
        @Scheduled(initialDelay = 0, fixedDelay = 1000)
        public void initData22() {
            long id = Thread.currentThread().getId();
    
            try {
                TimeUnit.SECONDS.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
        }
    
        @Scheduled(initialDelay = 0, fixedDelay = 2000)
        public void initData3333() {
    
    
        }
    
        // 我现在测试方便 希望当前这个项目 一启动就执行某个方法(操作一些bean对象)
    
        /**
         * 在当前对象的构造方法调用以后执行
         * spring在创建bean对象的时候 遵循了这个注解的规范
         * spring在当前bean对象实例化以后,属性注入完以后
         * 同步阻塞式执行该方法
         * 这个注解作用的方法必须是无参 无返回值 public修饰的
         */
        @PostConstruct
        public void initData() {
            // 查询数据库
            List<Goods> goodsList = goodsMapper.selectSeckillGoods();
            if (CollectionUtils.isEmpty(goodsList)) {
                return;
            }
            goodsList.forEach(goods -> {
                redisTemplate.opsForValue().set("goods_stock:" + goods.getGoodsId(), goods.getTotalStocks().toString());
            });
        }
    }
    
    

    1.5.5. 创建listener包下的-秒杀监听SeckillListener 类

    package com.powernode.listener;
    
    import com.powernode.service.GoodsService;
    import org.apache.rocketmq.common.message.MessageExt;
    import org.apache.rocketmq.spring.annotation.ConsumeMode;
    import org.apache.rocketmq.spring.annotation.MessageModel;
    import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
    import org.apache.rocketmq.spring.core.RocketMQListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     *
     * 时间: 2022-06-16 11:31
     */
    @Component
    @RocketMQMessageListener(topic = "seckillTopic",
            consumerGroup = "${rocketmq.consumer.group}",
            consumeMode = ConsumeMode.CONCURRENTLY,
            consumeThreadNumber = 20,
            maxReconsumeTimes = 5,
            messageModel = MessageModel.CLUSTERING
    )
    public class SeckillListener implements RocketMQListener<MessageExt> {
    
    
        @Autowired
        private GoodsService goodsService;
    
        /**
         * 处理秒杀业务方法
         *
         * @param message
         */
        @Override
        public void onMessage(MessageExt message) {
            String msg = new String(message.getBody());
            String[] split = msg.split("-");
            int userId = Integer.parseInt(split[0]);
            int goodsId = Integer.parseInt(split[1]);
            goodsService.realDoSeckill(userId, goodsId);
        }
    }
    
    

    1.5.6. 新建GoodsService

    package com.powernode.service;
    
    import com.powernode.domain.Goods;
    
    /**
     *
     * 时间: 2022-06-16 10:39
     */
    public interface GoodsService {
    
    
        int deleteByPrimaryKey(Integer goodsId);
    
        int insert(Goods record);
    
        int insertSelective(Goods record);
    
        Goods selectByPrimaryKey(Integer goodsId);
    
        int updateByPrimaryKeySelective(Goods record);
    
        int updateByPrimaryKey(Goods record);
    	//新增realDoSeckill 做秒杀方法
        void realDoSeckill(Integer userId, Integer goodsId);
    }
    
    

    1.5.7. 新建GoodsServiceImpl

    package com.powernode.service.impl;
    
    import com.powernode.domain.Order;
    import com.powernode.mapper.OrderMapper;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    import com.powernode.domain.Goods;
    import com.powernode.mapper.GoodsMapper;
    import com.powernode.service.GoodsService;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    
    /**
     *
     * 时间: 2022-06-16 10:39
     */
    @Service
    //@DependsOn(A.class)
    public class GoodsServiceImpl implements GoodsService {
    
        @Resource
        private GoodsMapper goodsMapper;
    
        @Autowired
        private OrderMapper orderMapper;
    
        @Autowired
        private RedissonClient redissonClient;
    
        private Long time = 1000L;
    
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
    
        @Override
        public int deleteByPrimaryKey(Integer goodsId) {
            return goodsMapper.deleteByPrimaryKey(goodsId);
        }
    
        @Override
        public int insert(Goods record) {
            return goodsMapper.insert(record);
        }
    
        @Override
        public int insertSelective(Goods record) {
            return goodsMapper.insertSelective(record);
        }
    
        @Override
        public Goods selectByPrimaryKey(Integer goodsId) {
            return goodsMapper.selectByPrimaryKey(goodsId);
        }
    
        @Override
        public int updateByPrimaryKeySelective(Goods record) {
            return goodsMapper.updateByPrimaryKeySelective(record);
        }
    
        @Override
        public int updateByPrimaryKey(Goods record) {
            return goodsMapper.updateByPrimaryKey(record);
        }
    
    
        /**
         * 做秒杀
         * 如果参与秒杀的商品比较多
         * 那么在mq里面的消息很多
         * 会导致数据的压力非常大 数据库连接有限
         * 我们尽可能减少排队的数量
         * ---------
         * 除了使用mysql的行锁以外 还可以使用其他的锁工具去处理这个问题
         * 如果数据库不是innodb 该如何去加锁 起到分布式锁的作用?
         *
         * 使用redis
         * 分布式锁
         * mysql的行锁**
         * redis setnx(set if not exist)*****
         * zookeeper
         *
         * @param userId
         * @param goodsId
         */
        //@Override
        //@Transactional(rollbackFor = RuntimeException.class)
        //public void realDoSeckill(Integer userId, Integer goodsId) {
        //    // 直接使用行锁 控制卖超问题
        //    int i = goodsMapper.updateGoodsStock(goodsId);
        //    if (i > 0) {
        //        // 写订单表
        //        writeOrder(goodsId, userId);
        //    }
        //}
    
    
        /**
         * redis分布式锁
         *
         * @param userId
         * @param goodsId
         */
        //@Override
        //@Transactional(rollbackFor = RuntimeException.class)
        //public void realDoSeckill(Integer userId, Integer goodsId) {
        //    // 当A B 两个线程同时进入去获取redis的key  B没有成功 那么B就结束了 在库存充足的情况下 不是很合理
        //    // 结合自旋来处理这个场景问题  自旋锁 (可以让这个线程在这里空转)
        //    // jedis#setnx == redisTemplate#setIfAbsent
        //    long currentTime = 0L;
        //    while (currentTime < time) {
        //        // 我们在设置锁的时候 建议添加一个过期时间 避免出现死锁的情况
        //        // 讨论业务代码执行超时问题   续命
        //        Boolean flag = redisTemplate.opsForValue().setIfAbsent("goods_lock:" + goodsId, "", 60, TimeUnit.SECONDS);
        //        // 我们可以拿到获取到所得这个线程的id
        //        // 当时间快过期了  我就去判断一下当前线程是否还在里面执行 3 次  如果超过一定的次数 就不处理了
        //        //long id = Thread.currentThread().getId();
        //        //redisTemplate.expire("goods_lock:" + goodsId, 60, TimeUnit.SECONDS);
        //        if (flag) {
        //            try {
        //                // 直接使用行锁 控制卖超问题
        //                int i = goodsMapper.updateGoodsStock(goodsId);
        //                if (i > 0) {
        //                    // 写订单表
        //                    writeOrder(goodsId, userId);
        //                }
        //                // 加了自旋注意return的时机
        //                return;
        //            } finally {
        //                // 一定会执行
        //                // 使用redis来做分布式锁 一定要删除掉
        //                redisTemplate.delete("goods_lock:" + goodsId);
        //            }
        //        } else {
        //            // 可以给定一些机会
        //            currentTime += 200;
        //            // 没获取成功 就走else
        //            try {
        //                Thread.sleep(200);
        //            } catch (InterruptedException e) {
        //                throw new RuntimeException(e);
        //            }
        //        }
        //    }
        //
        //
        //}
        @Override
        @Transactional(rollbackFor = RuntimeException.class)
        public void realDoSeckill(Integer userId, Integer goodsId) {
            RLock lock = redissonClient.getLock("goodsLock:" + goodsId);
            lock.lock(30, TimeUnit.SECONDS);
            // 直接使用行锁 控制卖超问题
            try {
                int i = goodsMapper.updateGoodsStock(goodsId);
                if (i > 0) {
                    // 写订单表
                    writeOrder(goodsId, userId);
                }
            } finally {
                lock.unlock();
            }
        }
    
        private void writeOrder(Integer goodsId, Integer userId) {
            Order order = new Order();
            order.setCreatetime(new Date());
            order.setUserid(userId);
            order.setGoodsid(goodsId);
            int i = orderMapper.insert(order);
            if (i <= 0) {
                throw new RuntimeException("写入订单表失败");
            }
        }
    }
    
    

    1.5.8 GoodsMapper

    package com.powernode.mapper;
    
    import com.powernode.domain.Goods;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    @Mapper
    public interface GoodsMapper {
        int deleteByPrimaryKey(Integer goodsId);
    
        int insert(Goods record);
    
        int insertSelective(Goods record);
    
        Goods selectByPrimaryKey(Integer goodsId);
    
        int updateByPrimaryKeySelective(Goods record);
    
        int updateByPrimaryKey(Goods record);
    
        List<Goods> selectSeckillGoods();
    
        int updateGoodsStock(Integer goodsId);
    }
    
    

    1.5.9. GoodsMapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mapper.GoodsMapper">
      <resultMap id="BaseResultMap" type="com.powernode.domain.Goods">
        
        
        <id column="goods_id" jdbcType="INTEGER" property="goodsId" />
        <result column="goods_name" jdbcType="VARCHAR" property="goodsName" />
        <result column="price" jdbcType="DECIMAL" property="price" />
        <result column="content" jdbcType="LONGVARCHAR" property="content" />
        <result column="status" jdbcType="INTEGER" property="status" />
        <result column="total_stocks" jdbcType="INTEGER" property="totalStocks" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
        <result column="spike" jdbcType="INTEGER" property="spike" />
      resultMap>
      <sql id="Base_Column_List">
        
        goods_id, goods_name, price, content, `status`, total_stocks, create_time, update_time, 
        spike
      sql>
      <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        
        select 
        <include refid="Base_Column_List" />
        from goods
        where goods_id = #{goodsId,jdbcType=INTEGER}
      select>
      <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
        
        delete from goods
        where goods_id = #{goodsId,jdbcType=INTEGER}
      delete>
      <insert id="insert" keyColumn="goods_id" keyProperty="goodsId" parameterType="com.powernode.domain.Goods" useGeneratedKeys="true">
        
        insert into goods (goods_name, price, content, 
          `status`, total_stocks, create_time, 
          update_time, spike)
        values (#{goodsName,jdbcType=VARCHAR}, #{price,jdbcType=DECIMAL}, #{content,jdbcType=LONGVARCHAR}, 
          #{status,jdbcType=INTEGER}, #{totalStocks,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, 
          #{updateTime,jdbcType=TIMESTAMP}, #{spike,jdbcType=INTEGER})
      insert>
      <insert id="insertSelective" keyColumn="goods_id" keyProperty="goodsId" parameterType="com.powernode.domain.Goods" useGeneratedKeys="true">
        
        insert into goods
        <trim prefix="(" suffix=")" suffixOverrides=",">
          <if test="goodsName != null and goodsName != ''">
            goods_name,
          if>
          <if test="price != null">
            price,
          if>
          <if test="content != null and content != ''">
            content,
          if>
          <if test="status != null">
            `status`,
          if>
          <if test="totalStocks != null">
            total_stocks,
          if>
          <if test="createTime != null">
            create_time,
          if>
          <if test="updateTime != null">
            update_time,
          if>
          <if test="spike != null">
            spike,
          if>
        trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
          <if test="goodsName != null and goodsName != ''">
            #{goodsName,jdbcType=VARCHAR},
          if>
          <if test="price != null">
            #{price,jdbcType=DECIMAL},
          if>
          <if test="content != null and content != ''">
            #{content,jdbcType=LONGVARCHAR},
          if>
          <if test="status != null">
            #{status,jdbcType=INTEGER},
          if>
          <if test="totalStocks != null">
            #{totalStocks,jdbcType=INTEGER},
          if>
          <if test="createTime != null">
            #{createTime,jdbcType=TIMESTAMP},
          if>
          <if test="updateTime != null">
            #{updateTime,jdbcType=TIMESTAMP},
          if>
          <if test="spike != null">
            #{spike,jdbcType=INTEGER},
          if>
        trim>
      insert>
      <update id="updateByPrimaryKeySelective" parameterType="com.powernode.domain.Goods">
        
        update goods
        <set>
          <if test="goodsName != null and goodsName != ''">
            goods_name = #{goodsName,jdbcType=VARCHAR},
          if>
          <if test="price != null">
            price = #{price,jdbcType=DECIMAL},
          if>
          <if test="content != null and content != ''">
            content = #{content,jdbcType=LONGVARCHAR},
          if>
          <if test="status != null">
            `status` = #{status,jdbcType=INTEGER},
          if>
          <if test="totalStocks != null">
            total_stocks = #{totalStocks,jdbcType=INTEGER},
          if>
          <if test="createTime != null">
            create_time = #{createTime,jdbcType=TIMESTAMP},
          if>
          <if test="updateTime != null">
            update_time = #{updateTime,jdbcType=TIMESTAMP},
          if>
          <if test="spike != null">
            spike = #{spike,jdbcType=INTEGER},
          if>
        set>
        where goods_id = #{goodsId,jdbcType=INTEGER}
      update>
      <update id="updateByPrimaryKey" parameterType="com.powernode.domain.Goods">
        
        update goods
        set goods_name = #{goodsName,jdbcType=VARCHAR},
          price = #{price,jdbcType=DECIMAL},
          content = #{content,jdbcType=LONGVARCHAR},
          `status` = #{status,jdbcType=INTEGER},
          total_stocks = #{totalStocks,jdbcType=INTEGER},
          create_time = #{createTime,jdbcType=TIMESTAMP},
          update_time = #{updateTime,jdbcType=TIMESTAMP},
          spike = #{spike,jdbcType=INTEGER}
        where goods_id = #{goodsId,jdbcType=INTEGER}
      update>
    
      <select id="selectSeckillGoods" resultMap="BaseResultMap">
        select goods_id ,total_stocks from goods where spike = 1 and `status` = 1
        select>
    
      <update id="updateGoodsStock">
        UPDATE goods
        SET total_stocks = total_stocks - 1,
            update_time = now()
        WHERE
                goods_id = #{value}
          AND total_stocks - 1 >= 0
      update>
    mapper>
    

    生成完成未改动

    OrderService–*

    package com.powernode.service;
    
    import com.powernode.domain.Order;
    
    public interface OrderService{
    
    
        int deleteByPrimaryKey(Integer id);
    
        int insert(Order record);
    
        int insertSelective(Order record);
    
        Order selectByPrimaryKey(Integer id);
    
        int updateByPrimaryKeySelective(Order record);
    
        int updateByPrimaryKey(Order record);
    
    }
    
    

    OrderServiceImpl–*

    package com.powernode.service.impl;
    
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    import com.powernode.domain.Order;
    import com.powernode.mapper.OrderMapper;
    import com.powernode.service.OrderService;
    
    @Service
    public class OrderServiceImpl implements OrderService{
    
        @Resource
        private OrderMapper orderMapper;
    
        @Override
        public int deleteByPrimaryKey(Integer id) {
            return orderMapper.deleteByPrimaryKey(id);
        }
    
        @Override
        public int insert(Order record) {
            return orderMapper.insert(record);
        }
    
        @Override
        public int insertSelective(Order record) {
            return orderMapper.insertSelective(record);
        }
    
        @Override
        public Order selectByPrimaryKey(Integer id) {
            return orderMapper.selectByPrimaryKey(id);
        }
    
        @Override
        public int updateByPrimaryKeySelective(Order record) {
            return orderMapper.updateByPrimaryKeySelective(record);
        }
    
        @Override
        public int updateByPrimaryKey(Order record) {
            return orderMapper.updateByPrimaryKey(record);
        }
    
    }
    
    

    OrderMapper–*

    package com.powernode.mapper;
    
    import com.powernode.domain.Order;
    import org.apache.ibatis.annotations.Mapper;
    
    
    @Mapper
    public interface OrderMapper {
        int deleteByPrimaryKey(Integer id);
    
        int insert(Order record);
    
        int insertSelective(Order record);
    
        Order selectByPrimaryKey(Integer id);
    
        int updateByPrimaryKeySelective(Order record);
    
        int updateByPrimaryKey(Order record);
    }
    
    

    OrderMapper.xml–*

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.powernode.mapper.OrderMapper">
      <resultMap id="BaseResultMap" type="com.powernode.domain.Order">
        
        
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="userid" jdbcType="INTEGER" property="userid" />
        <result column="goodsid" jdbcType="INTEGER" property="goodsid" />
        <result column="createtime" jdbcType="TIMESTAMP" property="createtime" />
      resultMap>
      <sql id="Base_Column_List">
        
        id, userid, goodsid, createtime
      sql>
      <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        
        select 
        <include refid="Base_Column_List" />
        from `order`
        where id = #{id,jdbcType=INTEGER}
      select>
      <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
        
        delete from `order`
        where id = #{id,jdbcType=INTEGER}
      delete>
      <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.powernode.domain.Order" useGeneratedKeys="true">
        
        insert into `order` (userid, goodsid, createtime
          )
        values (#{userid,jdbcType=INTEGER}, #{goodsid,jdbcType=INTEGER}, #{createtime,jdbcType=TIMESTAMP}
          )
      insert>
      <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.powernode.domain.Order" useGeneratedKeys="true">
        
        insert into `order`
        <trim prefix="(" suffix=")" suffixOverrides=",">
          <if test="userid != null">
            userid,
          if>
          <if test="goodsid != null">
            goodsid,
          if>
          <if test="createtime != null">
            createtime,
          if>
        trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
          <if test="userid != null">
            #{userid,jdbcType=INTEGER},
          if>
          <if test="goodsid != null">
            #{goodsid,jdbcType=INTEGER},
          if>
          <if test="createtime != null">
            #{createtime,jdbcType=TIMESTAMP},
          if>
        trim>
      insert>
      <update id="updateByPrimaryKeySelective" parameterType="com.powernode.domain.Order">
        
        update `order`
        <set>
          <if test="userid != null">
            userid = #{userid,jdbcType=INTEGER},
          if>
          <if test="goodsid != null">
            goodsid = #{goodsid,jdbcType=INTEGER},
          if>
          <if test="createtime != null">
            createtime = #{createtime,jdbcType=TIMESTAMP},
          if>
        set>
        where id = #{id,jdbcType=INTEGER}
      update>
      <update id="updateByPrimaryKey" parameterType="com.powernode.domain.Order">
        
        update `order`
        set userid = #{userid,jdbcType=INTEGER},
          goodsid = #{goodsid,jdbcType=INTEGER},
          createtime = #{createtime,jdbcType=TIMESTAMP}
        where id = #{id,jdbcType=INTEGER}
      update>
    mapper>
    

    完整代码

    链接:https://pan.baidu.com/s/1qoZm_eBp0v9ghbUIPcCe0g?pwd=0lqd
    提取码:0lqd

  • 相关阅读:
    gif表情制作一键轻松搞定,教你自制gif表情
    除了走路,40-60岁的人,还可以进行哪些运动?3种运动可供参考
    基于springboot实现休闲娱乐代理售票平台系统项目【项目源码+论文说明】计算机毕业设计
    文本标注技术方案(NLP标注工具)
    Day56-59 进程的状态、进程控制、进程通信方式
    linux文件锁
    PHP 电竞网站系统mysql数据库web结构apache计算机软件工程网页wamp
    《JAVA SE》包装类
    刷题记录(NC16645 [NOIP2007]矩阵取数游戏,NC207781 迁徙过程中的河流,NC235953 最大m个子段和)
    Java——JDK1.8新特性
  • 原文地址:https://blog.csdn.net/qq_39505065/article/details/126938814