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 ...
随机推荐
- [Java数据结构]使用Stack检查表达式中左右括号是否匹配
Stack是一种先进后出的数据结构后,这个特点决定了它在递归向下的场景中有独到的功效. 以下程序展示了它在检查表达式中括号匹配的有效性: 程序: package com.heyang.util; im ...
- python django 简单接口测试页面
项目创建订单只能是接口创建的,之前都是用jar包放到jmeter里调用下单,给产品或者运维用不太方便,就想用django写一个带前端界面的下单web程序 项目结构 代码,比较渣 # coding=ut ...
- 反向代理搭建隧道,服务器系统为Ubuntu18.04
该文章参考了实验室师兄写的教程,并记录了自己在实操过程中的坑. 1.内网机器配置 假设现在有一台公用服务器和一台内网服务器,现在想通过反向代理的方式来访问内网服务器.假设公用服务器为A,内网服务器为B ...
- 【吴恩达课程使用】pip安装pandas失败-anaconda各种玄学T-T-从新开始搭建环境
[吴恩达课程使用]安装pandas失败-从新开始搭建环境 在第五课第二周的任务2中,虚拟环境缺少pandas,sklearn依赖,因为用pip比较顺手,就直接使用pip安装,结果各种anaconda环 ...
- k8s通过service访问pod(五)
service 每个 Pod 都有自己的 IP 地址.当 controller 用新 Pod 替代发生故障的 Pod 时,新 Pod 会分配到新的 IP 地址.这样就产生了一个问题: 如果一组 Pod ...
- 天猫精灵对接1:outh对接
公司的智能家居产品需要接入语音控制,目前在对接阿里语音的天猫精灵 对接天猫精灵的第一步是完成outh鉴权 https://doc-bot.tmall.com/docs/doc.htm?spm=0.76 ...
- 面试官:讲讲Redis的五大数据类型?如何使用?(内含完整测试源码)
写在前面 最近面试跳槽的小伙伴有点多,给我反馈的面试情况更是千差万别,不过很多小伙伴反馈说:面试中的大部分问题都能够在我的公众号[冰河技术]中找到答案,面试过程还是挺轻松的,最终也是轻松的拿到了Off ...
- npm包的发布和管理
npm包管理 npm其实是Node.js的包管理工具(node package manager). 为啥我们需要一个包管理工具呢?因为我们在Node.js上开发时,会用到很多别人写的JavaScrip ...
- sping aop 源码分析(-)-- 代理对象的创建过程分析
测试项目已上传到码云,可以下载:https://gitee.com/yangxioahui/aopdemo.git 具体如下: public interface Calc { Integer add( ...
- MySQL 5.7主从复制
简介 主从复制是利用MySQL复制机制将数据复制到另外一台或多台MySQL服务器上,被复制的服务器称为主服务器,复制的服务器称为从服务器.一般是一主多从.主从复制的好处主要是数据备份.负载均衡(读写分 ...