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 ...
随机推荐
- 反射之hasattr() getattr() setattr() 函数
Python的hasattr() getattr() setattr() 函数使用方法详解 hasattr(object, name)判断object中有没有一个name字符串对应的方法或属性,返回B ...
- CentOS7重装yum和python
卸载现有的Python和Yum 1.删除现有Python ##强制删除已安装程序及其关联 rpm -qa|grep python|xargs rpm -ev --allmatches --nodeps ...
- 面向对象--继承-通过super()来调用父类方法
super()的使用 问题: class Master(object): def __init__(self): self.kongfu = "古法煎饼果子配方" # 实例变量,属 ...
- [LeetCode]21. 合并两个有序链表(递归)
题目 将两个有序链表合并为一个新的有序链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1->2->4, 1->3->4 输出:1->1-> ...
- 内存管理初始化源码5:free_area_init_nodes
start_kernel ——> setup_arch ——> arch_mem_init ——> |——> bootmem_init |——> device_tree ...
- Java学习预热
预备知识 什么是计算机 点击进入百度百科(计算机) 计算机硬件组成 主要部件 CPU 内存 主板 IO设备 键盘 鼠标 麦克风 显示器 音箱 冯·诺依曼体系结构 计算机软件 系统软件 DOS wind ...
- mariadb 1
mariadb(第一章) 数据库介绍 1.什么是数据库? 简单的说,数据库就是一个存放数据的仓库,这个仓库是按照一定的数据结构(数据结构是指数据的组织形式或数据之间的联系)来组织,存储的,我们 ...
- 性能之qps,并发数,相应时间
QPS:每秒处理的请求数.QPS = 并发数/请求平均处理时间. 请求响应时间=请求等待时间+网络时间+请求处理时间.假设请求处理时间不受影响,持续不变,实际请求数大于QPS,会影响请求响应时间,大量 ...
- ES6 常用总结——第三章(数组、函数、对象的扩展)
1.1. Array.from() Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结 ...
- 静态代理、jdk动态代理、cglib动态代理
一.静态代理 Subject:抽象主题角色,抽象主题类可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求. RealSubject:具体主题角色,也叫被委托角色.被代理角色.是业务逻辑 ...