IDEA SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统
先放上github地址:spike-system,可以直接下载完整项目运行测试
SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统
技术栈:SpringBoot, MySql, Redis, RabbitMQ, JPA,(lombok)
Controller
/put : 上架 "watch"商品10个
@RequestMapping("/put")
String put(@RequestParam String orderName, @RequestParam Long count)
/sec : 秒杀购买商品
@RequestMapping("/sec")
String sec(String userName, String orderName)
Guide
项目参考自
入门基础必备,使用Idea实现SpringBoot+Mysql+Redis+RabbitMQ+Jmeter模拟实现高并发秒杀
简化了原项目,将tkmybatis替换成JPA。并将一些比较复杂的操作尽可能简化。
项目结构
│ MainSystemApplication.java
│
├─config
│ MyRabbitConfig.java
│ MyRedisConfig.java
│
├─controller
│ Test.java
│
├─dao
│ StockRe.java
│ TOrderRe.java
│
├─domain
│ Stock.java
│ TOrder.java
│
└─service
MQOrderService.java
MQStockService.java
RedisService.java
说明
stock代表库存,TORder代表订单。当一个购买行为发生时,应用会先查询购买物品的库存是否足够,如果足够,则将库存减一,并生成一个订单数据到数据库保存,最后返回购买成功的消息给用户
START
首先利用idea创建springboot项目时勾选lombok\web\redis\RabbitMQ\MySql\JPA,生成的pom依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
首先要创建两个实体类到domain包下
package com.chunmiao.mainsystem.domain;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.io.Serializable;
/**
* 库存实体类
*/
@Data
@Entity
public class Stock implements Serializable {
@Id
@GeneratedValue
private Long id;
private String name;
//货品库存数量
private Long stock;
}
package com.chunmiao.mainsystem.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.io.Serializable;
/**
* 订单实体类
*/
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TOrder implements Serializable{
@Id
@GeneratedValue
private Long id;
private String orderName;
private String orderUser;
}
然后创建jpa的Repository到dao包下。
jpa实现crud的操作只需要继承JPARepository接口即可自动提供find\sava\delete实现类给用户使用。
package com.chunmiao.mainsystem.dao;
import com.chunmiao.mainsystem.domain.TOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TOrderRe extends JpaRepository<TOrder,Long> {
}
由于JPA默认只提供findById的操作,如果想通过别的字段查询需要自己提供接口,当然,JPA也会为这个接口自动提供实现类
package com.chunmiao.mainsystem.dao;
import com.chunmiao.mainsystem.domain.Stock;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface StockRe extends JpaRepository<Stock,Long> {
Stock findByName(String name);
}
然后创建config包,配置RabbitMQ和Redis.
RabbitMQ这里涉及到交换机、队列、路由键的概念,需要搜索RabbitMQ基础教程了解一下
package com.chunmiao.mainsystem.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRabbitConfig {
public final static String TORDER_EXCHANG = "TORDER_EXCHANG";
public final static String TORDER_QUEUE = "TORDER_QUEUE";
public final static String TORDER_ROUTING_KEY = "TORDER_ROUTING_KEY";
public final static String STOCK_EXCHANG = "STOCK_EXCHANG";
public final static String STOCK_QUEUE = "STOCK_QUEUE";
public final static String STOCK_ROUTING_KEY = "STOCK_ROUTING_KEY";
/**
* 订单消息
* 1.创建交换机
* 2.创建队列
* 3.通过路由键绑定交换机和队列
*/
@Bean
public Exchange getTOrderExchang() {
return ExchangeBuilder.directExchange(TORDER_EXCHANG).build();
}
@Bean
public Queue getTOrderQueue() {
return QueueBuilder.nonDurable(TORDER_QUEUE).build();
}
@Bean
public Binding bindTOrder() {
return BindingBuilder.bind(getTOrderQueue()).to(getTOrderExchang()).with(TORDER_ROUTING_KEY).noargs();
}
/**
* 库存消息
* 1.创建交换机
* 2.创建队列
* 3.通过路由键绑定交换机和队列
*/
@Bean
public Exchange getStockExchange() {
return ExchangeBuilder.directExchange(STOCK_EXCHANG).build();
}
@Bean
public Queue getStockQueue() {
return QueueBuilder.nonDurable(STOCK_QUEUE).build();
}
@Bean
public Binding bindStock() {
return BindingBuilder.bind(getStockQueue()).to(getStockExchange()).with(STOCK_ROUTING_KEY).noargs();
}
}
Redis的配置主要是序列化的配置,应该不配置也可以的。但是配置后查看的时候更美观,而且性能会更好更简洁。
package com.chunmiao.mainsystem.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> re = new RedisTemplate<>();
re.setConnectionFactory(redisConnectionFactory);
re.setKeySerializer(new StringRedisSerializer());
re.setValueSerializer(new Jackson2JsonRedisSerializer<>(Long.class)); // 不能用generic的Serializer,有存Long取Integer的bug
re.afterPropertiesSet();
return re;
}
}
然后到Service层,逻辑是RedisService提供增加库存、查询库存服务,增加库存时需要调用StockRe保存增加库存到数据库。
package com.chunmiao.mainsystem.service;
import com.chunmiao.mainsystem.dao.StockRe;
import com.chunmiao.mainsystem.domain.Stock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* stock信息缓存到redis
*/
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private StockRe stockRe;
public void put(String key, Long value) {
BoundValueOperations<String, Object> bp = redisTemplate.boundValueOps(key);
Long count = (Long) bp.get();
if ( count!= null){
count = count >= 0 ? count + value : value;
} else count = value;
bp.set(count);
Stock stock = stockRe.findByName(key);
if (stock == null) {
stock = new Stock();
stock.setName(key);
stock.setStock(0l);
}
long l = stock.getStock() + value;
stock.setStock(l);
stockRe.save(stock);
}
// 返回当前商品库存-1的结果,如果库存小于0时直接返回,这样调用它的类就知道已经没有库存了
public Long decrBy(String key) {
BoundValueOperations<String, Object> bp = redisTemplate.boundValueOps(key);
Long count = (Long) bp.get();
if (count == null) return -1l;
if (count >= 0) {
count--;
bp.set(count);
}
return count;
}
}
MQOrderService用于消费队列中的订单消息,创建新订单保存到数据库。只需要使用@RabbitMQListener即可实现监听
package com.chunmiao.mainsystem.service;
import com.chunmiao.mainsystem.dao.TOrderRe;
import com.chunmiao.mainsystem.domain.TOrder;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static com.chunmiao.mainsystem.config.MyRabbitConfig.TORDER_QUEUE;
import static com.chunmiao.mainsystem.config.MyRabbitConfig.STOCK_QUEUE;
/**
* 从MQ中拿消息,创建一个新订单到数据库
*/
@Service
public class MQOrderService {
@Autowired
private TOrderRe orderRe;
@RabbitListener(queues = TORDER_QUEUE)
public void saveOrder(TOrder order) {
System.out.println("创建新订单");
orderRe.save(order);
}
}
同理,监听库存消息并修改数据库值
package com.chunmiao.mainsystem.service;
import com.chunmiao.mainsystem.dao.StockRe;
import com.chunmiao.mainsystem.domain.Stock;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static com.chunmiao.mainsystem.config.MyRabbitConfig.STOCK_QUEUE;
/**
* 从MQ拿消息,使库存减少一个
*/
@Service
public class MQStockService {
@Autowired
private StockRe stockRe;
@RabbitListener(queues = STOCK_QUEUE)
public void decrStock(String orderName) {
System.out.println("减少数据库的库存");
Stock stock = stockRe.findByName(orderName);
if (stock!= null) {
stock.setStock(stock.getStock() - 1);
stockRe.save(stock);
}
}
}
最后一个类,controller,提供接口。购买逻辑是直接调用redisService提供的方法,实现操作 库存-1,返回该结果,如果结果>=0说明库存充足,发送创建新订单和库存-1消息给RabbitMQ,然后直接返回购买成功的结果给用户即可;如果库存不足,直接返回购买失败即可。使用RabbitMQ的好处是,只需要关心是否有库存,然后简单的发送消息之后就不用再管了,同时做到了削峰的好处
package com.chunmiao.mainsystem.controller;
import com.chunmiao.mainsystem.domain.TOrder;
import com.chunmiao.mainsystem.service.RedisService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static com.chunmiao.mainsystem.config.MyRabbitConfig.*;
@RestController
public class Test {
@Autowired
private RedisService redisService;
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/put")
String put(@RequestParam String orderName, @RequestParam Long count) {
redisService.put(orderName, count);
return "上架商品\n" + orderName + ":" + count;
}
@RequestMapping("/sec")
String sec(String userName, String orderName) {
String msg = "秒杀用户:" + userName + "\n" + "秒杀商品: " + orderName;
System.out.println("\n---------------------------------------------");
System.out.println("秒杀用户:" + userName + "\n" + "秒杀商品: " + orderName);
Long count = redisService.decrBy(orderName);
// 秒杀成功
System.out.println("当前商品数量为: " + (count + 1));
if (count >= 0) {
System.out.println("库存充足");
// 创建新订单
rabbitTemplate.convertAndSend(TORDER_EXCHANG,TORDER_ROUTING_KEY,
TOrder.builder()
.orderName(orderName)
.orderUser(userName)
.build());
// 创建库存-1消息
rabbitTemplate.convertAndSend(STOCK_EXCHANG,STOCK_ROUTING_KEY,orderName);
System.out.println("秒杀成功");
msg += "成功";
} else {
System.out.println("库存不足");
msg += "失败";
}
return msg;
}
}
最后可以运行起来,先put一个商品进去,再利用Jmeter进行压力测试,Jmeter的使用可以参考本文参考的文章。
IDEA SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统的更多相关文章
- springboot+jpa+mysql+redis+swagger整合步骤
springboot+jpa+MySQL+swagger框架搭建好之上再整合redis: 在电脑上先安装redis: 一.在pom.xml中引入redis 二.在application.yml里配置r ...
- 基于springboot+bootstrap+mysql+redis搭建一套完整的权限架构【六】【引入bootstrap前端框架】
https://blog.csdn.net/linzhefeng89/article/details/78752658 基于springboot+bootstrap+mysql+redis搭建一套完整 ...
- springboot+jpa+mysql+swagger整合
Springboot+jpa+MySQL+swagger整合 创建一个springboot web项目 <dependencies> <dependency> < ...
- spring-boot jpa mysql emoji utfmb4 异常处理
spring-boot jpa mysql utf8mb4 emoji 写入失败 mysql database,table,column 默认为utf8mb4 Caused by: java.sql. ...
- Springboot Jpa: [mysql] java.sql.SQLException: Duplicate entry 'XXX' for key 'PRIMARY'
前言 1.问题背景 偶尔会出现登录请求出错的情况,一旦失败就会短时间内再也登录不上,更换浏览器或者刷新可能会暂时解决这个问题. 项目运行日志如下: 2022-07-21 09:43:40.946 DE ...
- SpringBoot+Jpa+SpringSecurity+Redis+Vue的前后端分离开源系统
项目简介: eladmin基于 Spring Boot 2.1.0 . Jpa. Spring Security.redis.Vue的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 ...
- 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战
大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...
- SpringBoot+Jpa+MySql学习
上一篇介绍了springboot简单整合mybatis的教程.这一篇是介绍springboot简单整合jpa的教程. 由于jpa的功能强大,后续会继续写关于jpa的介绍已经使用,本文只是简单介绍一下它 ...
- docker-compose部署mysql,redis,rabbitmq
version: '3' services: mysql: image: mysql:5.7.31 container_name: mysql restart: always command: --c ...
随机推荐
- 20190923-11Linux crond 系统定时任务 000 019
crond 服务管理 1.重新启动crond服务 [root@hadoop101 ~]# service crond restart centOS7是 systemctl restart crond ...
- JUC使用
1.什么是JUC 源码 + 官方文档 面试高频问! java.util 工具包.包.分类 业务:普通的线程代码 Thread Runnable 没有返回值.效率相比入 Callable 相对较低! 2 ...
- 【漏洞复现篇】CVE-2020-1472-微软NetLogon权限提升-手把手教学-简单域环境搭建与Exp执行
一.漏洞简介 NetLogon 远程协议是一种在 Windows 域控上使用的 RPC 接口,被用于各种与用户和机器认证相关的任务.最常用于让用户使用 NTLM 协议登录服务器,也用于 NTP 响应认
- appcan 文件下载与预览
用appcan开发的app如何在手机上查看附件和预览附件呢?今天就为大家介绍一下,用APP看附件实大是太方便了. 1.直接上代码吧,首先要初始化插件用到的所有方法.这个方法中 cbIsFileExis ...
- Python-变量-字符串
str 字符串如何表示字符串? 单行 单引号 '' 如果字符串中有单引号就需要双引号表示,反之亦然 双引号 " " 换行表示 \ one_str = "简洁胜于优雅&qu ...
- makefile的隐式规则
target := exe source_code = hello.c OBJS = $(source_code:.c=.o) $(target):$(OBJS) gcc $^ -o $@ clean ...
- 《Java从入门到失业》第五章:继承与多态(5.1-5.7):继承
5.继承与多态 5.1为什么要继承 最近我儿子迷上了一款吃鸡游戏<香肠派对>,无奈给他买了许多玩具枪,我数了下,有一把狙击枪AWM,一把步枪AK47,一把重机枪加特林(Gatling).假 ...
- Webstorm破解版安装教程
Webstorm破解版: 安装包链接见:https://pan.baidu.com/s/1XJqRtM9C4M8AmH50S9dVDQ 提取码: dah3 内附安装教程, 原创文章,转载请先联系作者
- python数据结构之图深度优先和广度优先实例详解
本文实例讲述了python数据结构之图深度优先和广度优先用法.分享给大家供大家参考.具体如下: 首先有一个概念:回溯 回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到 ...
- C++重载>>和<<(输入和输出运算符)详解
转载:http://c.biancheng.net/view/2311.html 在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进行了重载,使其能够用于不同数据 ...